我的理解:为什么obj.style.backgroundColor有时候取不到值?

本文通过实例探讨了在JavaScript中如何可靠地获取HTML元素的背景颜色等样式属性。针对非内联样式无法直接通过obj.style获取的问题,提供了一个实用的getRealStyle函数来解决此难题。

obj.style.backgroundColor有时候能取到值,有时候取不到,这是为什么呢?看了下面的代码你就知道了。

<html>
    <head>
        <style type="text/css">
	    p {background-color: #ccc}
	</style>
	</head>
	<body>
	    <p id="demo" class="p">非内联样式。</p>
		<p id="demo2" style="height:50px; width:250px; margin-top:10px; text-align:center; line-height:50px; background-color:#ccc;">内联样式。</p>
	</body>
</html>

<script type="text/javascript">
    document.getElementById("demo").onclick = function() {
        alert(
            "高度:"+this.style.height+"\n"+
            "宽度:"+this.style.width+"\n"+
            "上边距:"+this.style.marginTop+"\n"+
            "对齐:"+this.style.textAlign+"\n"+
            "行高:"+this.style.lineHeight+"\n"+
            "背景颜色:"+this.style.backgroundColor
        );
        alert(
            "高度:"+getRealStyle(this, "height")+"\n"+
            "宽度:"+getRealStyle(this, "width")+"\n"+
            "上边距:"+getRealStyle(this, "marginTop")+"\n"+
            "对齐:"+getRealStyle(this, "textAlign")+"\n"+
            "行高:"+getRealStyle(this, "lineHeight")+"\n"+
            "背景颜色:"+getRealStyle(this, "backgroundColor")
        );
    };
    document.getElementById("demo2").onclick = function() {
        alert(
            "高度:"+this.style.height+"\n"+
            "宽度:"+this.style.width+"\n"+
            "上边距:"+this.style.marginTop+"\n"+
            "对齐:"+this.style.textAlign+"\n"+
            "行高:"+this.style.lineHeight+"\n"+
            "背景颜色:"+this.style.backgroundColor
        );
    };

    // 一定要取到值怎么办?可以用下面这个方法。
    function getRealStyle(obj, styleName) {
        var realStyle = null;
        if(obj.currentStyle) {
            realStyle = obj.currentStyle[styleName];
        } else if(window.getComputedStyle) {
            realStyle = window.getComputedStyle(obj, null)[styleName];
        }
        return realStyle;
    }
</script>

 我个人的理解和结论就是内联样式是可以取到值的,否则是取不到的,一定要取到值怎么办?可以用代码中的getRealStyle(obj, styleName)获取。

优化代码,使结果用单独一个窗口显示,解决窗口不能显示的问题,给我优化后完整的代码,代码:classdef TDMPerformanceAnalyzer < TDMSystem % TDMPERFORMANCEANALYZER TDM系统性能分析工具 % 继承自TDMSystem基类,提供全面的性能分析功能 properties snrRange % SNR测试范围 (dB) performanceCurves % 性能曲线数据结构 detailedMetrics % 详细性能指标 convergenceAnalysis % 收敛性分析结果 modulationAnalysis % 调制方式对比分析 end properties (Access = private) originalParams % 原始参数备份 end methods function obj = TDMPerformanceAnalyzer(params) % 构造函数 % 输入: params - 系统参数结构体 % 调用父类构造函数 obj = obj@TDMSystem(params); % 备份原始参数 obj.originalParams = params; % 设置默认SNR范围 obj.snrRange = 0:2:20; % 0dB到20dB,步长2dB end function obj = runPerformanceAnalysis(obj, customSNR) % 主性能分析方法 % 输入: customSNR - (可选)自定义SNR范围 % 处理可选参数 if nargin > 1 && ~isempty(customSNR) validateattributes(customSNR, {'numeric'}, {'vector', 'nonempty'}, ... 'runPerformanceAnalysis', 'customSNR'); obj.snrRange = customSNR; end try % 初始化性能数据结构 numSignals = obj.params.numSignals; numSNR = length(obj.snrRange); % 预分配内存 obj.performanceCurves = struct(... 'ber', zeros(numSNR, numSignals), ... 'snr', zeros(numSNR, numSignals), ... 'mse', zeros(numSNR, numSignals), ... 'throughput', zeros(numSNR, numSignals)); % 保存原始SNR设置 originalSNR = obj.params.snrDb; % 进度条显示 hWaitbar = waitbar(0, '正在执行性能分析...', ... 'Name', 'TDM性能分析', ... 'CreateCancelBtn', 'setappdata(gcbf,''canceling'',1)'); setappdata(hWaitbar, 'canceling', 0); % 遍历不同SNR for snrIdx = 1:numSNR % 检查消操作 if getappdata(hWaitbar, 'canceling') break; end % 更新SNR参数 obj.params.snrDb = obj.snrRange(snrIdx); % 调用超类方法 obj = runSimulation(obj); % 存储性能指标 obj.performanceCurves.ber(snrIdx, :) = obj.performance.ber; obj.performanceCurves.snr(snrIdx, :) = obj.performance.snr; obj.performanceCurves.mse(snrIdx, :) = obj.performance.mse; % 计算吞吐量 (1 - BER) obj.performanceCurves.throughput(snrIdx, :) = 1 - obj.performance.ber; % 更新进度条 waitbar(snrIdx/numSNR, hWaitbar, ... sprintf('正在分析 SNR=%.1f dB (%.0f%%)', ... obj.snrRange(snrIdx), snrIdx/numSNR*100)); end % 关闭进度条 delete(hWaitbar); % 恢复原始SNR obj.params.snrDb = originalSNR; % 执行详细分析 - 确保方法名完全匹配 obj = performDetailedAnalysis(obj); obj = performConvergenceAnalysis(obj); obj = analyzeModulationImpact(obj); catch ME % 错误处理 if exist('hWaitbar', 'var') && ishandle(hWaitbar) delete(hWaitbar); end rethrow(ME); end end function obj = performDetailedAnalysis(obj) % 详细性能指标计算 numSignals = obj.params.numSignals; numSNR = length(obj.snrRange); % 初始化结构体 obj.detailedMetrics = struct(... 'avgBER', zeros(1, numSignals), ... 'avgSNR', zeros(1, numSignals), ... 'minBER', zeros(1, numSignals), ... 'maxThroughput', zeros(1, numSignals), ... 'snrSensitivity', zeros(1, numSignals), ... 'berAt10dB', zeros(1, numSignals), ... 'berAt20dB', zeros(1, numSignals)); for i = 1:numSignals % 提当前信号的性能数据 berData = obj.performanceCurves.ber(:, i); snrData = obj.performanceCurves.snr(:, i); throughputData = obj.performanceCurves.throughput(:, i); % 平均BER和SNR obj.detailedMetrics.avgBER(i) = mean(berData); obj.detailedMetrics.avgSNR(i) = mean(snrData); % 最低BER obj.detailedMetrics.minBER(i) = min(berData); % 最大吞吐量 obj.detailedMetrics.maxThroughput(i) = max(throughputData); % SNR敏感性 (BER改善率) berDiff = diff(berData); snrDiff = diff(obj.snrRange'); obj.detailedMetrics.snrSensitivity(i) = mean(berDiff ./ snrDiff); % 特定SNR点的BER idx10dB = find(obj.snrRange >= 10, 1, 'first'); idx20dB = find(obj.snrRange >= 20, 1, 'first'); if ~isempty(idx10dB) obj.detailedMetrics.berAt10dB(i) = berData(idx10dB); end if ~isempty(idx20dB) && idx20dB <= numSNR obj.detailedMetrics.berAt20dB(i) = berData(idx20dB); end end end function obj = performConvergenceAnalysis(obj, threshold) % 收敛性分析 % 输入: threshold - (可选)收敛判断阈 if nargin < 2 || isempty(threshold) threshold = 1e-4; % 默认收敛判断阈 end numSignals = obj.params.numSignals; obj.convergenceAnalysis = struct(... 'convergencePoint', zeros(1, numSignals), ... 'stableBER', zeros(1, numSignals), ... 'convergenceSNR', zeros(1, numSignals)); for i = 1:numSignals berValues = obj.performanceCurves.ber(:, i); % 找到BER变化小于阈的点 berDiff = abs(diff(berValues)); convergenceIdx = find(berDiff < threshold, 1, 'first'); if isempty(convergenceIdx) obj.convergenceAnalysis.convergencePoint(i) = length(obj.snrRange); obj.convergenceAnalysis.stableBER(i) = berValues(end); obj.convergenceAnalysis.convergenceSNR(i) = obj.snrRange(end); else obj.convergenceAnalysis.convergencePoint(i) = convergenceIdx; obj.convergenceAnalysis.stableBER(i) = berValues(convergenceIdx); obj.convergenceAnalysis.convergenceSNR(i) = obj.snrRange(convergenceIdx); end end end function obj = analyzeModulationImpact(obj) % 调制方式影响分析 if isfield(obj.params, 'signalModulations') && ~isempty(obj.params.signalModulations) modTypes = unique(obj.params.signalModulations); else % 如果没有指定调制方式,默认为所有信号相同 modTypes = {'Unknown'}; obj.params.signalModulations = repmat(modTypes, 1, obj.params.numSignals); end numModTypes = length(modTypes); obj.modulationAnalysis = struct(... 'modulationTypes', {modTypes}, ... 'avgBER', zeros(1, numModTypes), ... 'avgSNR', zeros(1, numModTypes), ... 'sensitivity', zeros(1, numModTypes), ... 'minBER', zeros(1, numModTypes), ... 'maxThroughput', zeros(1, numModTypes)); for m = 1:numModTypes modType = modTypes{m}; signalIndices = find(strcmp(obj.params.signalModulations, modType)); if isempty(signalIndices) continue; end % 提该调制方式的所有性能数据 berValues = obj.performanceCurves.ber(:, signalIndices); snrValues = obj.performanceCurves.snr(:, signalIndices); throughputValues = obj.performanceCurves.throughput(:, signalIndices); % 计算平均性能 obj.modulationAnalysis.avgBER(m) = mean(berValues(:)); obj.modulationAnalysis.avgSNR(m) = mean(snrValues(:)); % 计算灵敏度 (BER随SNR的变化率) meanBer = mean(berValues, 2); berDiff = diff(meanBer); snrDiff = diff(obj.snrRange'); obj.modulationAnalysis.sensitivity(m) = mean(berDiff ./ snrDiff); % 最佳性能 obj.modulationAnalysis.minBER(m) = min(berValues(:)); obj.modulationAnalysis.maxThroughput(m) = max(throughputValues(:)); end end function figHandle = showAnalysisResults(obj, figHandle) % 在独立窗口中显示分析结果 % 输入: figHandle - (可选)现有图形句柄 % 输出: figHandle - 图形句柄 % 创建独立结果窗口 if nargin < 2 || isempty(figHandle) || ~isvalid(figHandle) figHandle = figure('Name', 'TDM系统性能综合分析', ... 'Position', [100, 100, 1400, 900], ... 'Color', 'w', ... 'NumberTitle', 'off', ... 'MenuBar', 'none', ... 'ToolBar', 'figure'); else figure(figHandle); clf; end % 创建主布局 mainLayout = uix.VBox('Parent', figHandle, 'Padding', 5); % 标题面板 titlePanel = uipanel('Parent', mainLayout, ... 'BorderType', 'none', ... 'BackgroundColor', [0.8 0.9 1]); titleText = sprintf('TDM系统综合分析 (策略: %s, 总时隙: %d)', ... obj.params.strategy, obj.params.totalSlots); uicontrol('Parent', titlePanel, ... 'Style', 'text', ... 'String', titleText, ... 'FontSize', 16, ... 'FontWeight', 'bold', ... 'BackgroundColor', [0.8 0.9 1], ... 'Position', [10, 10, 1380, 40]); % 选项卡组容器 tabContainer = uix.VBox('Parent', mainLayout); % 创建选项卡组 tabGroup = uitabgroup('Parent', tabContainer); % 选项卡1: 核心性能指标 tab1 = uitab(tabGroup, 'Title', '核心指标'); axesGrid = uix.Grid('Parent', tab1, 'Spacing', 10); % 1. BER vs SNR曲线 ax1 = axes('Parent', uicontainer('Parent', axesGrid)); hold(ax1, 'on'); colors = lines(obj.params.numSignals); for i = 1:obj.params.numSignals % 处理可能的零BER logBER = log10(max(obj.performanceCurves.ber(:, i), 1e-20)); plot(ax1, obj.snrRange, logBER, ... 'Color', colors(i, :), 'LineWidth', 2, ... 'DisplayName', sprintf('信号 %d', i)); end hold(ax1, 'off'); title(ax1, '误码率(BER) vs 信噪比(SNR)'); xlabel(ax1, 'SNR (dB)'); ylabel(ax1, 'log_{10}(BER)'); grid(ax1, 'on'); legend(ax1, 'show', 'Location', 'best'); % 2. 吞吐量 vs SNR曲线 ax2 = axes('Parent', uicontainer('Parent', axesGrid)); hold(ax2, 'on'); for i = 1:obj.params.numSignals plot(ax2, obj.snrRange, obj.performanceCurves.throughput(:, i), ... 'Color', colors(i, :), 'LineWidth', 2, ... 'DisplayName', sprintf('信号 %d', i)); end hold(ax2, 'off'); title(ax2, '吞吐量 vs 信噪比(SNR)'); xlabel(ax2, 'SNR (dB)'); ylabel(ax2, '吞吐量 (1 - BER)'); grid(ax2, 'on'); legend(ax2, 'Location', 'best'); % 3. 调制方式性能对比 ax3 = axes('Parent', uicontainer('Parent', axesGrid)); modTypes = obj.modulationAnalysis.modulationTypes; numModTypes = length(modTypes); barValues = [obj.modulationAnalysis.avgBER; ... obj.modulationAnalysis.avgSNR]; bar(ax3, barValues'); set(ax3, 'XTickLabel', modTypes); title(ax3, '调制方式性能对比'); ylabel(ax3, '性能指标'); legend(ax3, {'平均BER', '平均SNR'}, 'Location', 'best'); grid(ax3, 'on'); % 添加数标签 for i = 1:numModTypes if barValues(1, i) > 0 text(ax3, i, barValues(1, i), sprintf('%.2e', barValues(1, i)), ... 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'center'); end text(ax3, i, barValues(2, i), sprintf('%.1f dB', barValues(2, i)), ... 'VerticalAlignment', 'top', 'HorizontalAlignment', 'center'); end % 4. 收敛点分析 ax4 = axes('Parent', uicontainer('Parent', axesGrid)); convergencePoints = obj.convergenceAnalysis.convergencePoint; stableBER = obj.convergenceAnalysis.stableBER; yyaxis(ax4, 'left'); bar(ax4, convergencePoints, 'FaceColor', [0.2 0.6 0.8]); ylabel(ax4, '收敛点 (SNR索引)'); yyaxis(ax4, 'right'); % 处理可能的零BER logStableBER = log10(max(stableBER, 1e-20)); plot(ax4, logStableBER, 'o-', 'LineWidth', 2, 'MarkerSize', 8); ylabel(ax4, 'log_{10}(稳定BER)'); title(ax4, '信号收敛分析'); xlabel(ax4, '信号编号'); grid(ax4, 'on'); legend(ax4, {'收敛点', '稳定BER'}, 'Location', 'best'); % 5. SNR敏感性分析 ax5 = axes('Parent', uicontainer('Parent', axesGrid)); sensitivity = obj.detailedMetrics.snrSensitivity; bar(ax5, sensitivity, 'FaceColor', [0.8 0.2 0.2]); title(ax5, 'SNR敏感性分析'); xlabel(ax5, '信号编号'); ylabel(ax5, 'BER变化率 / dB'); grid(ax5, 'on'); % 添加数标签 for i = 1:length(sensitivity) text(ax5, i, sensitivity(i), sprintf('%.2e', sensitivity(i)), ... 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'center'); end % 6. 综合性能评分 ax6 = axes('Parent', uicontainer('Parent', axesGrid)); % 性能评分公式: 权重分配 performanceScore = 0.4 * (1 - obj.detailedMetrics.avgBER) + ... 0.3 * (obj.detailedMetrics.avgSNR / 30) + ... 0.2 * obj.detailedMetrics.maxThroughput + ... 0.1 * (1 - abs(obj.detailedMetrics.snrSensitivity)); bar(ax6, performanceScore, 'FaceColor', [0.5 0.2 0.8]); title(ax6, '信号综合性能评分'); xlabel(ax6, '信号编号'); ylabel(ax6, '性能评分 (0-1)'); ylim(ax6, [0 1]); grid(ax6, 'on'); % 添加数标签 for i = 1:length(performanceScore) text(ax6, i, performanceScore(i), sprintf('%.3f', performanceScore(i)), ... 'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'center'); end % 设置网格布局 set(axesGrid, 'Widths', [-1 -1 -1], 'Heights', [-1 -1]); % 选项卡2: 详细数据视图 tab2 = uitab(tabGroup, 'Title', '详细数据'); tablePanel = uipanel('Parent', tab2, 'BorderType', 'none'); tableData = obj.getPerformanceTableData(); uitable('Parent', tablePanel, ... 'Data', tableData, ... 'ColumnName', {'信号', '调制方式', '平均BER', '平均SNR(dB)', '最低BER', '最大吞吐量', 'SNR敏感性', '10dB BER', '20dB BER'}, ... 'Units', 'normalized', ... 'Position', [0.05, 0.05, 0.9, 0.9], ... 'FontSize', 10); % 选项卡3: 收敛性分析 tab3 = uitab(tabGroup, 'Title', '收敛性'); convGrid = uix.HBox('Parent', tab3, 'Spacing', 10); % 收敛点可视化 convPanel1 = uipanel('Parent', convGrid, 'Title', '收敛点可视化'); ax7 = axes('Parent', convPanel1); hold(ax7, 'on'); for i = 1:obj.params.numSignals berData = obj.performanceCurves.ber(:, i); convergenceSNR = obj.convergenceAnalysis.convergenceSNR(i); convergenceIdx = obj.convergenceAnalysis.convergencePoint(i); % 处理可能的零BER logBER = log10(max(berData, 1e-20)); plot(ax7, obj.snrRange, logBER, ... 'Color', colors(i, :), 'LineWidth', 2, ... 'DisplayName', sprintf('信号 %d', i)); % 标记收敛点 plot(ax7, convergenceSNR, logBER(convergenceIdx), ... 'o', 'MarkerSize', 10, 'MarkerFaceColor', colors(i, :)); end hold(ax7, 'off'); title(ax7, 'BER收敛点'); xlabel(ax7, 'SNR (dB)'); ylabel(ax7, 'log_{10}(BER)'); grid(ax7, 'on'); legend(ax7, 'show', 'Location', 'best'); % 收敛SNR convPanel2 = uipanel('Parent', convGrid, 'Title', '收敛SNR'); ax8 = axes('Parent', convPanel2); convergenceSNR = obj.convergenceAnalysis.convergenceSNR; bar(ax8, convergenceSNR, 'FaceColor', [0.3 0.7 0.5]); title(ax8, '各信号收敛所需SNR'); xlabel(ax8, '信号编号'); ylabel(ax8, '收敛SNR (dB)'); grid(ax8, 'on'); % 设置布局比例 set(convGrid, 'Widths', [-2 -1]); % 添加导出按钮 buttonPanel = uix.HButtonBox('Parent', mainLayout, 'Padding', 10); uicontrol('Parent', buttonPanel, ... 'Style', 'pushbutton', ... 'String', '导出到Excel', ... 'Callback', @(src,evt) obj.exportToExcel()); uicontrol('Parent', buttonPanel, ... 'Style', 'pushbutton', ... 'String', '关闭窗口', ... 'Callback', @(src,evt) close(figHandle)); % 设置主布局比例 set(mainLayout, 'Heights', [50 -1 40]); % 确保图形更新 drawnow; end function tableData = getPerformanceTableData(obj) % 生成性能数据表格 numSignals = obj.params.numSignals; tableData = cell(numSignals, 9); for i = 1:numSignals tableData{i, 1} = i; % 信号编号 % 调制方式 if isfield(obj.params, 'signalModulations') && length(obj.params.signalModulations) >= i tableData{i, 2} = obj.params.signalModulations{i}; else tableData{i, 2} = 'N/A'; end % 性能数据 tableData{i, 3} = sprintf('%.2e', obj.detailedMetrics.avgBER(i)); tableData{i, 4} = sprintf('%.2f', obj.detailedMetrics.avgSNR(i)); tableData{i, 5} = sprintf('%.2e', obj.detailedMetrics.minBER(i)); tableData{i, 6} = sprintf('%.4f', obj.detailedMetrics.maxThroughput(i)); tableData{i, 7} = sprintf('%.2e', obj.detailedMetrics.snrSensitivity(i)); tableData{i, 8} = sprintf('%.2e', obj.detailedMetrics.berAt10dB(i)); tableData{i, 9} = sprintf('%.2e', obj.detailedMetrics.berAt20dB(i)); end end function exportToExcel(obj, filename) % 导出性能数据到Excel % 输入: filename - 输出文件名 if nargin < 2 [file, path] = uiputfile('*.xlsx', '保存性能分析结果', ... 'TDM_Performance_Analysis.xlsx'); if isequal(file, 0) disp('用户消导出操作'); return; end filename = fullfile(path, file); end % 创建数据表 sheet1 = obj.getPerformanceTableData(); headers = {'信号', '调制方式', '平均BER', '平均SNR(dB)', '最低BER', '最大吞吐量', 'SNR敏感性', '10dB BER', '20dB BER'}; % 写入详细性能数据 writetable(cell2table([headers; sheet1]), filename, 'Sheet', '详细性能'); % 写入SNR扫描数据 snrHeaders = {'SNR(dB)'}; for i = 1:obj.params.numSignals snrHeaders{end+1} = sprintf('信号%d_BER', i); snrHeaders{end+1} = sprintf('信号%d_吞吐量', i); end snrData = num2cell([obj.snrRange', ... obj.performanceCurves.ber, ... obj.performanceCurves.throughput]); writetable(cell2table([snrHeaders; snrData]), filename, 'Sheet', 'SNR扫描'); % 写入调制方式分析 modHeaders = {'调制方式', '平均BER', '平均SNR(dB)', '灵敏度', '最低BER', '最大吞吐量'}; modData = cell(length(obj.modulationAnalysis.modulationTypes), 6); for m = 1:length(obj.modulationAnalysis.modulationTypes) modData{m, 1} = obj.modulationAnalysis.modulationTypes{m}; modData{m, 2} = obj.modulationAnalysis.avgBER(m); modData{m, 3} = obj.modulationAnalysis.avgSNR(m); modData{m, 4} = obj.modulationAnalysis.sensitivity(m); modData{m, 5} = obj.modulationAnalysis.minBER(m); modData{m, 6} = obj.modulationAnalysis.maxThroughput(m); end writetable(cell2table([modHeaders; modData]), filename, 'Sheet', '调制分析'); disp(['性能数据已导出到: ' filename]); % 显示导出成功消息 msgbox(sprintf('性能分析结果已成功导出到:\n%s', filename), ... '导出完成', 'help'); end end methods (Static) function analyzer = analyzeSystem(params, customSNR) % 静态方法: 完整分析工作流 % 输入: params - 系统参数 % customSNR - (可选)自定义SNR范围 analyzer = TDMPerformanceAnalyzer(params); if nargin > 1 analyzer = runPerformanceAnalysis(analyzer, customSNR); else analyzer = runPerformanceAnalysis(analyzer); end % 在新窗口中显示结果 figure = showAnalysisResults(analyzer); % 等待窗口关闭 uiwait(figure); % 提示用户导出 choice = questdlg('是否导出分析结果?', '导出选项', ... '是', '否', '是'); if strcmp(choice, '是') exportToExcel(analyzer); end end end end
06-25
classdef EmotionRecognitionSystem < handle % 面部表情识别系统主类 % 支持自定义数据集训练和表情识别 properties fig % 主窗口句柄 net % 表情识别模型 axImg % 图像显示区域 txtResult % 结果显示文本框 cam % 摄像头对象 videoPlayer % 视频播放器 isCapturing = false; % 是否正在捕获视频 end methods function obj = EmotionRecognitionSystem() % 构造函数 - 创建GUI并加载模型 % 创建主窗口 obj.fig = figure('Name', '面部表情识别系统', ... 'Position', [300 200 900 550], ... 'NumberTitle', 'off', ... 'MenuBar', 'none', ... 'CloseRequestFcn', @obj.closeApp); % 尝试加载现有模型 try load('customEmotionNet.mat', 'net'); obj.net = net; disp('自定义模型加载成功'); catch obj.net = []; end % 创建UI控件 uicontrol('Style', 'pushbutton', ... 'Position', [30 480 120 40], ... 'String', '选择图片', ... 'Callback', @obj.loadImage, ... 'FontSize', 11); uicontrol('Style', 'pushbutton', ... 'Position', [170 480 120 40], ... 'String', '摄像头捕获', ... 'Callback', @obj.captureFromCam, ... 'FontSize', 11); uicontrol('Style', 'pushbutton', ... 'Position', [310 480 120 40], ... 'String', '实时识别', ... 'Callback', @obj.realTimeRecognition, ... 'FontSize', 11); uicontrol('Style', 'pushbutton', ... 'Position', [450 480 120 40], ... 'String', '训练模型', ... 'Callback', @obj.trainModel, ... 'FontSize', 11); uicontrol('Style', 'pushbutton', ... 'Position', [590 480 120 40], ... 'String', '帮助', ... 'Callback', @obj.showHelp, ... 'FontSize', 11); % 创建图像显示区域 obj.axImg = axes('Parent', obj.fig, ... 'Position', [0.05 0.1 0.5 0.7], ... 'Box', 'on'); axis(obj.axImg, 'image'); title(obj.axImg, '输入图像'); % 创建结果显示区域 uicontrol('Style', 'text', ... 'Position', [600 400 250 40], ... 'String', '识别结果:', ... 'FontSize', 14, ... 'FontWeight', 'bold', ... 'BackgroundColor', [0.9 0.9 0.9]); obj.txtResult = uicontrol('Style', 'text', ... 'Position', [600 300 250 90], ... 'String', '等待识别...', ... 'FontSize', 16, ... 'FontWeight', 'bold', ... 'ForegroundColor', [0 0.5 0], ... 'BackgroundColor', [0.95 0.95 0.95], ... 'HorizontalAlignment', 'center'); % 置信度指示器 uicontrol('Style', 'text', ... 'Position', [600 200 250 30], ... 'String', '置信度:', ... 'FontSize', 12, ... 'BackgroundColor', [0.9 0.9 0.9]); % 表情标签 emotions = {'平静', '大笑', '微笑', '哭泣', '悲伤'}; for i = 1:length(emotions) uicontrol('Style', 'text', ... 'Position', [600 170-(i-1)*30 100 25], ... 'String', emotions{i}, ... 'FontSize', 11, ... 'HorizontalAlignment', 'left', ... 'BackgroundColor', [0.95 0.95 0.95]); end end function loadImage(obj, ~, ~) % 从文件选择图像 [file, path] = uigetfile({'*.jpg;*.png;*.bmp;*.png', '图像文件'}); if isequal(file, 0) return; end imgPath = fullfile(path, file); obj.processImage(imread(imgPath)); end function captureFromCam(obj, ~, ~) % 从摄像头捕获图像 if isempty(webcamlist) errordlg('未检测到摄像头', '硬件错误'); return; end try obj.cam = webcam; preview(obj.cam); uicontrol('Style', 'text', ... 'Position', [300 10 300 30], ... 'String', '正在捕获...3秒后自动拍照', ... 'FontSize', 12, ... 'ForegroundColor', 'r'); pause(3); img = snapshot(obj.cam); clear obj.cam; obj.processImage(img); delete(findobj(obj.fig, 'Type', 'uicontrol', 'Position', [300 10 300 30])); catch ME errordlg(['摄像头错误: ' ME.message], '硬件错误'); if ~isempty(obj.cam) clear obj.cam; end end end function realTimeRecognition(obj, ~, ~) % 实时表情识别 if obj.isCapturing obj.isCapturing = false; return; end if isempty(webcamlist) errordlg('未检测到摄像头', '硬件错误'); return; end if isempty(obj.net) warndlg('请先训练模型', '模型缺失'); return; end obj.isCapturing = true; obj.cam = webcam; obj.videoPlayer = vision.DeployableVideoPlayer; % 创建停止按钮 stopBtn = uicontrol('Style', 'pushbutton', ... 'Position', [310 480 120 40], ... 'String', '停止识别', ... 'Callback', @obj.stopRealTime, ... 'FontSize', 11, ... 'BackgroundColor', [1 0.6 0.6]); % 实时识别循环 while obj.isCapturing && ishandle(obj.fig) img = snapshot(obj.cam); [emotion, confidence, faceImg] = obj.recognizeEmotion(img); % 在图像上标注结果 if ~isempty(faceImg) bbox = faceImg.bbox; label = sprintf('%s (%.1f%%)', emotion, confidence*100); img = insertObjectAnnotation(img, 'rectangle', bbox, ... label, 'Color', 'green', 'FontSize', 18); end step(obj.videoPlayer, img); end % 清理 release(obj.videoPlayer); clear obj.cam; delete(stopBtn); end function stopRealTime(obj, ~, ~) % 停止实时识别 obj.isCapturing = false; end function processImage(obj, img) % 处理单张图像 if isempty(obj.net) warndlg('请先训练模型', '模型缺失'); return; end axes(obj.axImg); imshow(img); title('输入图像'); % 表情识别 [emotion, confidence, faceImg] = obj.recognizeEmotion(img); % 显示结果 resultStr = sprintf('表情: %s\n置信度: %.2f%%', emotion, confidence*100); set(obj.txtResult, 'String', resultStr); % 绘制检测框 if ~isempty(faceImg) hold on; rectangle('Position', faceImg.bbox, ... 'EdgeColor', 'g', 'LineWidth', 2); text(faceImg.bbox(1), faceImg.bbox(2)-20, ... [emotion ' (' num2str(round(confidence*100)) '%)'], ... 'Color', 'g', 'FontSize', 14, 'FontWeight', 'bold'); hold off; end end function [emotion, confidence, faceImg] = recognizeEmotion(obj, img) % 表情识别函数 faceImg = []; % 人脸检测 faceDetector = vision.CascadeObjectDetector(); bbox = step(faceDetector, img); if isempty(bbox) emotion = '未检测到人脸'; confidence = 0; return; end % 选择最大的人脸区域 [~, idx] = max(bbox(:,3).*bbox(:,4)); mainBbox = bbox(idx,:); % 裁剪人脸区域 faceImg.img = imcrop(img, mainBbox); faceImg.bbox = mainBbox; % 预处理 - 保持RGB三通道 resizedImg = imresize(faceImg.img, [48 48]); % 模型输入尺寸 normalizedImg = im2single(resizedImg); % 归一化 % 表情分类 [pred, scores] = classify(obj.net, normalizedImg); confidence = max(scores); % 映射到表情标签 emotionMap = containers.Map(... {'neutral', 'happy', 'smile', 'cry', 'sad'}, ... {'平静', '大笑', '微笑', '哭泣', '悲伤'}); if isKey(emotionMap, char(pred)) emotion = emotionMap(char(pred)); else emotion = '未知表情'; end end function trainModel(obj, ~, ~) % 训练自定义模型 answer = questdlg('训练模型需要较长时间,是否继续?', ... '模型训练', '是', '否', '是'); if ~strcmp(answer, '是') return; end % 创建训练进度窗口 trainFig = figure('Name', '模型训练', ... 'Position', [400 300 400 200], ... 'NumberTitle', 'off', ... 'MenuBar', 'none'); uicontrol('Style', 'text', ... 'Position', [50 150 300 30], ... 'String', '正在训练模型,请稍候...', ... 'FontSize', 12); progressBar = uicontrol('Style', 'text', ... 'Position', [50 100 300 20], ... 'String', '', ... 'BackgroundColor', [0.8 0.8 1]); % 在单独的函数中训练模型 try obj.net = obj.trainCustomModel(progressBar); save('customEmotionNet.mat', 'net'); msgbox('模型训练完成并已保存!', '训练成功'); catch ME errordlg(['训练错误: ' ME.message], '训练失败'); end close(trainFig); end function net = trainCustomModel(obj, progressBar) % 自定义模型训练函数 datasetPath = uigetdir(pwd, '选择表情数据集目录'); if datasetPath == 0 error('未选择数据集'); end imds = imageDatastore(datasetPath, ... 'IncludeSubfolders', true, ... 'LabelSource', 'foldernames'); % 检查类别数量 classes = categories(imds.Labels); if numel(classes) ~= 5 error('数据集必须包含5个类别: neutral, happy, smile, cry, sad'); end % 数据集分割 [trainImgs, valImgs] = splitEachLabel(imds, 0.7, 'randomized'); % 更新进度 set(progressBar, 'Position', [50 100 100 20], 'String', '数据加载完成'); pause(0.5); % 数据增强 augmenter = imageDataAugmenter(... 'RandXReflection', true, ... 'RandRotation', [-15 15], ... 'RandXScale', [0.8 1.2], ... 'RandYScale', [0.8 1.2]); % 使用RGB图像 (48x48x3) augTrainImgs = augmentedImageDatastore([48 48], trainImgs, ... 'DataAugmentation', augmenter, ... 'OutputSizeMode', 'resize'); augValImgs = augmentedImageDatastore([48 48], valImgs, ... 'OutputSizeMode', 'resize'); % 更新进度 set(progressBar, 'Position', [50 100 150 20], 'String', '数据预处理完成'); pause(0.5); % 构建自定义CNN模型 (支持RGB输入) layers = [ % 修改输入层为48x48x3以支持RGB图像 imageInputLayer([48 48 3], 'Name', 'input') convolution2dLayer(5, 32, 'Padding', 'same', 'Name', 'conv1') batchNormalizationLayer('Name', 'bn1') reluLayer('Name', 'relu1') maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool1') convolution2dLayer(3, 64, 'Padding', 'same', 'Name', 'conv2') batchNormalizationLayer('Name', 'bn2') reluLayer('Name', 'relu2') maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool2') convolution2dLayer(3, 128, 'Padding', 'same', 'Name', 'conv3') batchNormalizationLayer('Name', 'bn3') reluLayer('Name', 'relu3') maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool3') fullyConnectedLayer(256, 'Name', 'fc1') reluLayer('Name', 'relu4') dropoutLayer(0.5, 'Name', 'dropout1') fullyConnectedLayer(5, 'Name', 'fc2') % 5个输出类别 softmaxLayer('Name', 'softmax') classificationLayer('Name', 'classoutput') ]; % 训练选项 options = trainingOptions('adam', ... 'InitialLearnRate', 0.001, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.5, ... 'LearnRateDropPeriod', 5, ... 'MaxEpochs', 20, ... 'MiniBatchSize', 64, ... 'Shuffle', 'every-epoch', ... 'ValidationData', augValImgs, ... 'ValidationFrequency', 50, ... 'Verbose', true, ... 'Plots', 'none', ... 'OutputFcn', @(info)obj.trainingProgress(info, progressBar)); % 更新进度 set(progressBar, 'Position', [50 100 200 20], 'String', '开始训练模型...'); drawnow; % 训练模型 net = trainNetwork(augTrainImgs, layers, options); end function trainingProgress(obj, info, progressBar) % 训练进度回调函数 if strcmp(info.State, 'iteration') % 计算整个训练过程的进度 progress = (info.Epoch - 1 + info.Iteration/info.NumIterations) / info.NumEpochs; set(progressBar, 'Position', [50 100 round(300*progress) 20], ... 'String', sprintf('训练进度: %.1f%%', progress*100)); drawnow; end end function showHelp(obj, ~, ~) % 显示帮助信息 helpText = { '面部表情识别系统使用指南' '1. 选择图片: 从文件系统加载图像进行识别' '2. 摄像头捕获: 使用摄像头拍摄照片进行识别' '3. 实时识别: 开启摄像头进行实时表情识别' '4. 训练模型: 使用自定义数据集训练新模型' '' '使用说明:' '- 首次使用需点击"训练模型"创建新模型' '- 训练完成后模型将保存为customEmotionNet.mat' '- 支持识别表情: 平静、大笑、微笑、哭泣、悲伤' '' '数据集要求:' '- 创建文件夹结构:' ' dataset/neutral' ' dataset/happy' ' dataset/smile' ' dataset/cry' ' dataset/sad' '- 每个子文件夹包含对应表情的图片' '- 图片格式支持JPG/PNG/BMP' '- 建议每个类别至少100张图片' '- 图像尺寸建议为48x48像素(系统会自动调整)' }; helpdlg(helpText, '系统帮助'); end function closeApp(obj, ~, ~) % 关闭应用程序 if obj.isCapturing obj.isCapturing = false; pause(0.5); end if ~isempty(obj.cam) clear obj.cam; end if ishandle(obj.fig) delete(obj.fig); end end end end保持训练图像与输入层一致matlab完整代码
06-11
请问如何S地图层级控制:「『 // qrc:/qml/common/DiscoverPage/PersonalRidingPage.qml import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import QtPositioning 5.15 import Qt5Compat.GraphicalEffects import QtWebView 1.15 import "qrc:/qml/common/DiscoverPage/" import App.Models 1.0 import RadioApp.Permission 1.0 Page { id: root background: Rectangle { color: "#0f0f14" } // 使用 RidingDataModel 的属性 property real todayTotalDistance: RidingDataModel ? RidingDataModel.todayTotalDistance : 0.0 property real currentRideDistance: RidingDataModel ? RidingDataModel.currentDistance / 1000.0 : 0.0 // 米转公里 property real currentSpeed: RidingDataModel ? RidingDataModel.currentSpeed * 3.6 : 0.0 // m/s 转 km/h property real maxSpeed: RidingDataModel ? RidingDataModel.maxSpeed * 3.6 : 0.0 // m/s 转 km/h property real maxAltitude: RidingDataModel ? RidingDataModel.maxAltitude : 0.0 property int ridingTime: RidingDataModel ? RidingDataModel.ridingTime : 0 property string formattedRidingTime: formatTime(ridingTime) // 从 RidingDataModel 获状态 property bool isRiding: RidingDataModel ? RidingDataModel.isRiding : false property bool isPaused: RidingDataModel ? RidingDataModel.isPaused : false property string lastRecordId: RidingDataModel ? RidingDataModel.recordId : "" // 状态标志 property bool isDestroyed: false property bool isInitialized: false property bool permissionsGranted: false property bool permissionsChecking: false // 地图控制按钮属性 property string btnBlue: "#007bff" property real btnSize: 0.6 property bool requestCenter: false // JavaScript 通信相关的属性 - 修改为直接使用 HTTP URL //property string mapUrl: "https://www.xjsfly.com/radio_map/map.html" // 直接使用网络URL property string mapUrl: "http://119.91.39.85/maps/map.html" // 直接使用网络URL property bool mapLoadedFromWeb: false property bool mapLoading: false // 平台特定调整 property real platformScale: Qt.platform.os === "android" ? 1.2 : 1.0 property real buttonTopMargin: Qt.platform.os === "android" ? 50 : 30 property real buttonSideMargin: Qt.platform.os === "android" ? 25 : 20 // 权限管理器 property PermissionManager permissionManager: PermissionManager { id: permissionManager onPermissionGranted: { console.log("权限已授予:", permissionTypeToString(permissionType)) checkAllRequiredPermissions() } onPermissionDenied: { console.log("权限被拒绝:", permissionTypeToString(permissionType)) showPermissionDeniedDialog(permissionType) } onAllRequiredPermissionsGranted: { console.log("所有必需权限已授予") permissionsGranted = true permissionsChecking = false // 权限就绪后继续初始化 if (!isInitialized) { continueInitialization() } } onEssentialPermissionsGranted: { console.log("核心权限已授予") permissionsGranted = true } onShowPermissionRationale: { console.log("显示权限解释:", rationale) showPermissionRationaleDialog(permissionType, rationale) } onShowPermissionExplanation: { console.log("显示权限说明:", title, message) showPermissionExplanationDialog(permissionType, title, message) } } // 时间格式化函数 function formatTime(seconds) { if (isDestroyed) return "00:00"; var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds % 3600) / 60); var secs = seconds % 60; if (hours > 0) { return hours + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (secs < 10 ? "0" + secs : secs); } else { return (minutes < 10 ? "0" + minutes : minutes) + ":" + (secs < 10 ? "0" + secs : secs); } } // 权限检查函数 function checkLocationPermission() { if (isDestroyed) return false; if (positionSource.supportedPositioningMethods === PositionSource.NoPositioningMethods) { console.error("设备不支持定位功能"); showPermissionDialog("定位功能不可用", "您的设备不支持定位功能,请检查设备设置。"); return false; } if (positionSource.active === false) { console.warn("定位服务未激活"); showPermissionDialog("定位权限", "请确保已授予应用定位权限,并在系统设置中开启定位服务。"); return false; } return true; } // 检查所有必需的权限 function checkAllRequiredPermissions() { console.log("开始检查所有必需权限...") permissionsChecking = true // 使用 PermissionManager 的枚举 var locationGranted = permissionManager.checkPermission(permissionManager.LocationPermission) var coarseLocationGranted = permissionManager.checkPermission(permissionManager.CoarseLocationPermission) var backgroundLocationGranted = permissionManager.checkPermission(permissionManager.BackgroundLocationPermission) console.log("定位权限状态 - 精确定位:", locationGranted, "粗略定位:", coarseLocationGranted, "后台定位:", backgroundLocationGranted) if (locationGranted && coarseLocationGranted) { permissionsGranted = true permissionsChecking = false console.log("定位权限已授予,继续初始化") if (!isInitialized) { continueInitialization() } } else { console.log("定位权限未完全授予,请求权限...") requestLocationPermissions() } } // 请求定位相关权限 function requestLocationPermissions() { if (isDestroyed) return; console.log("请求定位相关权限...") // 使用智能上下文请求权限 permissionManager.requestSmartPermissions("navigation_app") } // 权限类型转字符串 function permissionTypeToString(permissionType) { // 这里 permissionType 已经是数字,直接比较即可 if (permissionType === permissionManager.LocationPermission) return "精确定位" if (permissionType === permissionManager.CoarseLocationPermission) return "粗略定位" if (permissionType === permissionManager.BackgroundLocationPermission) return "后台定位" if (permissionType === permissionManager.CameraPermission) return "相机" if (permissionType === permissionManager.MicrophonePermission) return "麦克风" if (permissionType === permissionManager.StoragePermission) return "存储" if (permissionType === permissionManager.BluetoothPermission) return "蓝牙" return "未知权限" } // 显示权限解释对话框 function showPermissionRationaleDialog(permissionType, rationale) { if (isDestroyed) return; var component = Qt.createComponent("qrc:/qml/common/PermissionRationaleDialog.qml"); if (component.status === Component.Ready) { var dialog = component.createObject(root, { permissionType: permissionType, rationale: rationale, permissionManager: permissionManager }); dialog.open(); } else { // 回退到通用对话框 showPermissionDialog("权限说明", rationale) } } // 显示权限说明对话框 function showPermissionExplanationDialog(permissionType, title, message) { if (isDestroyed) return; showPermissionDialog(title, message) } // 显示权限被拒绝对话框 function showPermissionDeniedDialog(permissionType) { if (isDestroyed) return; var permissionName = permissionTypeToString(permissionType) var message = permissionName + "权限被拒绝,相关功能将无法正常使用。" var suggestion = "请前往应用设置中手动启用此权限" showPermissionDialog("权限被拒绝", message + "\n\n" + suggestion) } // 通用权限对话框 function showPermissionDialog(title, message) { if (isDestroyed) return; var component = Qt.createComponent("qrc:/qml/common/CommonDialog.qml"); if (component.status === Component.Ready) { var dialog = component.createObject(root, { title: title, message: message }); dialog.open(); } } // 继续初始化(权限检查通过后) function continueInitialization() { if (isDestroyed || isInitialized) return; console.log("权限检查通过,继续初始化...") console.log("使用新的布局结构 - 地图区域:", mapContainer.width + "x" + mapContainer.height) console.log("按钮覆盖层区域:", buttonsOverlay.width + "x" + buttonsOverlay.height) // 直接使用网络URL加载地图 loadMapFromWeb() initTimer.start() // 安卓上延迟调试按钮布局 if (Qt.platform.os === "android") { Qt.callLater(function() { if (!isDestroyed) { debugButtonLayout() // 安卓上可能需要额外的刷新 Qt.callLater(forceButtonsRefresh) } }) } } // 从网络加载地图文件 function loadMapFromWeb() { if (isDestroyed) return; console.log("使用网络URL加载地图...") mapLoading = true // 直接使用网络URL console.log("设置地图 URL:", mapUrl) webView.url = mapUrl } // 更新统计数据 function updateStats() { if (!permissionsGranted) return; todayTotalDistance = RidingDataModel ? RidingDataModel.todayTotalDistance : 0.0; currentRideDistance = RidingDataModel ? RidingDataModel.currentDistance / 1000.0 : 0.0; currentSpeed = RidingDataModel ? RidingDataModel.currentSpeed * 3.6 : 0.0; maxSpeed = RidingDataModel ? RidingDataModel.maxSpeed * 3.6 : 0.0; maxAltitude = RidingDataModel ? RidingDataModel.maxAltitude : 0.0; ridingTime = RidingDataModel ? RidingDataModel.ridingTime : 0; console.log("更新统计数据:", "今日总距离:", todayTotalDistance.toFixed(2), "当前距离:", currentRideDistance.toFixed(2), "当前速度:", currentSpeed.toFixed(1), "最高速度:", maxSpeed.toFixed(1), "最高海拔:", maxAltitude.toFixed(0)); } // JavaScript 调用函数 function executeJavaScript(code) { if (isDestroyed || !webView || !permissionsGranted) return; try { webView.runJavaScript(code); } catch (error) { console.error("执行 JavaScript 失败:", error); } } // 处理来自 JavaScript 的消息 function handleJavaScriptMessage(url) { if (isDestroyed || !permissionsGranted || !url) return; var urlString = url.toString(); if (urlString.indexOf("qtbridge://") === -1) return; try { var message = urlString.split("qtbridge://")[1]; var parts = message.split("/"); var action = parts[0].toLowerCase(); // 转换为小写统一处理 var data = parts.length > 1 ? parts[1] : ""; console.log("收到 JavaScript 消息:", action, data); switch(action) { case "mapready": console.log("高德地图初始化完成"); handleMapReady(); break; case "distancecalculated": var distance = parseFloat(data); console.log("JavaScript计算距离:", distance, "米"); handleDistanceCalculated(distance); break; case "positionupdated": var coords = data.split(","); var lat = parseFloat(coords[0]); var lng = parseFloat(coords[1]); console.log("地图位置更新:", lat, lng); break; case "error": console.error("JavaScript错误:", decodeURIComponent(data)); break; default: console.log("未知消息类型:", action); } } catch (error) { console.error("处理JavaScript消息失败:", error); } } // 清除地图轨迹 function clearMapTrack() { executeJavaScript(` if (window.clearTrack) { window.clearTrack(); } else if (window.qtTrackLine && window.qtMap) { window.qtTrackPoints = []; window.qtTrackLine.setPath([]); window.qtTotalDistance = 0; console.log("轨迹已清除"); } `); } // 添加轨迹点 function addTrackPoint(latitude, longitude) { executeJavaScript(` if (window.addTrackPoint) { window.addTrackPoint(${latitude}, ${longitude}); } else if (window.qtTrackLine && window.qtMap && window.qtMarker) { try { var point = new AMap.LngLat(${longitude}, ${latitude}); window.qtTrackPoints.push(point); window.qtTrackLine.setPath(window.qtTrackPoints); // 更新标记位置 window.qtMarker.setPosition(point); // 计算距离 if (window.qtTrackPoints.length > 1) { var total = 0; for (var i = 1; i < window.qtTrackPoints.length; i++) { total += window.qtTrackPoints[i-1].distance(window.qtTrackPoints[i]); } window.qtTotalDistance = total; // 发送距离到Qt window.location.href = "qtbridge://distanceCalculated/" + total; } // 自动调整地图视野 if (window.qtTrackPoints.length === 1) { window.qtMap.setCenter(point); } else { window.qtMap.setFitView(); } console.log("添加轨迹点:", ${latitude}, ${longitude}); } catch (error) { console.error("添加轨迹点失败:", error); } } `); } // 设置地图中心 function setMapCenter(latitude, longitude) { executeJavaScript(` if (window.setMapCenter) { window.setMapCenter(${latitude}, ${longitude}); } else if (window.qtMap && window.qtMarker) { try { var point = new AMap.LngLat(${longitude}, ${latitude}); window.qtMap.setCenter(point); window.qtMarker.setPosition(point); console.log("设置地图中心:", ${latitude}, ${longitude}); } catch (error) { console.error("设置地图中心失败:", error); } } `); } // 处理JavaScript计算的距离 function handleDistanceCalculated(distance) { if (isDestroyed || !RidingDataModel) return; console.log("收到JavaScript计算的距离:", distance, "米"); RidingDataModel.updateDistance(distance); } // 处理地图就绪 function handleMapReady() { if (isDestroyed) return; console.log("高德地图准备就绪"); // 地图就绪后可以执行一些初始化操作 } // 调试按钮布局 function debugButtonLayout() { if (isDestroyed) return console.log("=== 按钮布局调试信息 ===") console.log("平台:", Qt.platform.os) console.log("屏幕尺寸:", root.width, "x", root.height) console.log("地图容器尺寸:", mapContainer.width, "x", mapContainer.height) console.log("按钮覆盖层尺寸:", buttonsOverlay.width, "x", buttonsOverlay.height) console.log("按钮覆盖层z:", buttonsOverlay.z) console.log("WebView z:", webView.z) var buttons = [ {name: "速度按钮", obj: speedButton}, {name: "功能按钮组", obj: functionButtons}, {name: "中心按钮", obj: centerButton}, {name: "倾角按钮", obj: tiltButton}, {name: "打卡按钮", obj: checkinButton} ] buttons.forEach(function(btn) { if (btn.obj) { console.log(btn.name + ":", "可见:", btn.obj.visible, "位置:", btn.obj.x + "," + btn.obj.y, "尺寸:", btn.obj.width + "x" + btn.obj.height, "z:", btn.obj.z, "图层启用:", btn.obj.layer ? btn.obj.layer.enabled : "N/A") } else { console.log(btn.name + ": 未定义") } }) } // 强制刷新按钮显示 function forceButtonsRefresh() { if (isDestroyed) return console.log("强制刷新按钮显示状态") // 临时隐藏再显示,强制重绘 var buttons = [speedButton, functionButtons, centerButton, tiltButton, checkinButton] buttons.forEach(function(button) { if (button) { var wasVisible = button.visible button.visible = false Qt.callLater(function() { if (button && !isDestroyed) { button.visible = wasVisible console.log("刷新按钮:", button.objectName || "未知", "位置:", button.x, button.y) } }) } }) } // 在 Component.onCompleted 中添加更多调试信息 Component.onCompleted: { console.log("个人骑行页面初始化开始"); console.log("RidingDataModel 可用:", RidingDataModel !== undefined); console.log("PermissionManager 可用:", permissionManager !== undefined); console.log("地图URL:", mapUrl); console.log("WebView 组件状态:", webView !== undefined); console.log("当前平台:", Qt.platform.os); // 安卓特定初始化 if (Qt.platform.os === "android") { console.log("安卓平台检测到,应用特定优化") // 调整按钮大小和位置以适应安卓 btnSize = 0.8 } // 调试权限类型 permissionManager.debugPermissionTypes(); // 首先检查权限 checkAllRequiredPermissions(); } Component.onDestruction: { console.log("个人骑行页面开始销毁"); isDestroyed = true; // 停止所有定时器 statsTimer.stop(); initTimer.stop(); positionSource.active = false; console.log("个人骑行页面销毁完成"); } // 延迟初始化定时器 Timer { id: initTimer interval: 500 running: false repeat: false onTriggered: { if (isDestroyed || !permissionsGranted) return; console.log("执行延迟初始化"); updateStats(); // 启动统计更新定时器 statsTimer.start(); isInitialized = true; console.log("个人骑行页面初始化完成"); } } // 定时器用于定期更新统计 Timer { id: statsTimer interval: 1000 running: false repeat: true onTriggered: { if (!isDestroyed && isInitialized && permissionsGranted) { updateStats(); } } } // 权限检查覆盖层 Rectangle { id: permissionOverlay anchors.fill: parent color: "#CC000000" visible: !permissionsGranted || permissionsChecking z: 1000 Column { anchors.centerIn: parent spacing: 20 BusyIndicator { id: permissionBusyIndicator running: permissionsChecking width: 50 height: 50 anchors.horizontalCenter: parent.horizontalCenter } Text { text: permissionsChecking ? "正在检查权限..." : "需要定位权限才能使用骑行功能" color: "white" font.pixelSize: 16 anchors.horizontalCenter: parent.horizontalCenter } Button { text: "请求权限" visible: !permissionsChecking && !permissionsGranted anchors.horizontalCenter: parent.horizontalCenter onClicked: { requestLocationPermissions() } } Text { text: "骑行功能需要精确定位权限来记录您的轨迹和位置" color: "#CCCCCC" font.pixelSize: 14 width: parent.width * 0.8 wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter } } } // 地图加载状态覆盖层 Rectangle { id: mapLoadingOverlay anchors.fill: parent color: "#CC000000" visible: mapLoading z: 999 Column { anchors.centerIn: parent spacing: 20 BusyIndicator { id: mapLoadingIndicator running: mapLoading width: 50 height: 50 anchors.horizontalCenter: parent.horizontalCenter } Text { text: "正在加载地图..." color: "white" font.pixelSize: 16 anchors.horizontalCenter: parent.horizontalCenter } Text { text: "请确保网络连接正常" color: "#CCCCCC" font.pixelSize: 14 width: parent.width * 0.8 wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter } // 添加重试按钮 Button { text: "重试加载" visible: !mapLoadingIndicator.running && mapLoading anchors.horizontalCenter: parent.horizontalCenter onClicked: { console.log("手动重试地图加载") webView.reload() } } } } // 状态变化监听 - 监听 RidingDataModel Connections { id: ridingDataModelConnections target: RidingDataModel enabled: !isDestroyed && isInitialized && RidingDataModel !== undefined && permissionsGranted function onIsRidingChanged() { if (isDestroyed) return; console.log("骑行状态改变 - 是否骑行:", RidingDataModel.isRiding); Qt.callLater(updateStats); } function onIsPausedChanged() { if (isDestroyed) return; console.log("暂停状态改变 - 是否暂停:", RidingDataModel.isPaused); Qt.callLater(updateStats); } function onCurrentDistanceChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onCurrentSpeedChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onRidingTimeChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onMaxSpeedChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onMaxAltitudeChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } function onTodayTotalDistanceChanged() { if (isDestroyed) return; Qt.callLater(updateStats); } } // 主要修改:使用Column布局分离地图和数据面板 Item { anchors.fill: parent enabled: permissionsGranted opacity: permissionsGranted ? 1.0 : 0.3 // 使用Column布局分离地图和数据面板 Column { anchors.fill: parent spacing: 0 // 地图区域 - 固定高度 Item { id: mapContainer width: parent.width height: parent.height * 28/33 z: 0 Component.onCompleted: { console.log("地图容器创建完成,尺寸:", width, "x", height) } // 使用 WebView 显示地图 WebView { id: webView anchors.fill: parent visible: !isDestroyed && permissionsGranted && !mapLoading url: mapUrl z: 0 Component.onCompleted: { if (Qt.platform.os === "android") { console.log("安卓 WebView 配置: 启用混合内容和文件访问"); } } onLoadingChanged: function(loadRequest) { if (isDestroyed || !permissionsGranted) return; console.log("=== 页面加载状态 ==="); console.log("当前平台:", Qt.platform.os); console.log("页面加载状态:", loadRequest.status); console.log("页面URL:", webView.url); console.log("请求URL:", loadRequest.url); console.log("错误信息:", loadRequest.errorString); if (loadRequest.url.toString().indexOf("qtbridge://") === 0) { console.log("忽略 qtbridge 通信请求,状态:", loadRequest.status); return; } switch(loadRequest.status) { case WebView.LoadStartedStatus: console.log("开始加载地图页面"); mapLoading = true; break; case WebView.LoadSucceededStatus: console.log("地图页面加载成功"); mapLoading = false; mapLoadedFromWeb = true; // Android上尝试设置透明背景 if (Qt.platform.os === "android") { Qt.callLater(function() { if (!isDestroyed) { webView.runJavaScript(` try { document.body.style.backgroundColor = 'transparent'; document.body.style.overflow = 'hidden'; if (document.querySelector('.amap-container')) { document.querySelector('.amap-container').style.backgroundColor = 'transparent'; } } catch(e) { console.log('设置透明背景失败:', e); } `); } }); } if (Qt.platform.os === "android") { Qt.callLater(forceButtonsRefresh) } break; case WebView.LoadFailedStatus: console.error("地图页面加载失败:", loadRequest.errorString); console.error("错误详情:", { url: loadRequest.url, errorString: loadRequest.errorString }); mapLoading = false; showMapLoadError(loadRequest.errorString); break; } } onUrlChanged: { if (isDestroyed || !permissionsGranted) return; var urlString = webView.url.toString(); console.log("URL改变:", urlString); if (urlString.indexOf("qtbridge://") === 0) { handleJavaScriptMessage(webView.url); } } } // 定位源 PositionSource { id: positionSource active: false updateInterval: 1000 preferredPositioningMethods: PositionSource.AllPositioningMethods onPositionChanged: { if (isDestroyed || !permissionsGranted) { active = false; return; } if (requestCenter && position.latitudeValid && position.longitudeValid) { var coord = position.coordinate; setMapCenter(coord.latitude, coord.longitude); requestCenter = false; active = false; console.log("回到中心位置:", coord.latitude, coord.longitude); } } onSourceErrorChanged: { if (isDestroyed || !permissionsGranted) return; if (sourceError !== PositionSource.NoError) { console.error("定位错误:", sourceError); requestCenter = false; active = false; } } } } // 运动数据区域 - 固定高度 Rectangle { id: dataPanel width: parent.width height: parent.height * 5/33 color: "#333333" z: 10 // 确保在按钮层之上 visible: !isDestroyed && permissionsGranted property real rowSpacing: 10 property real columnSpacing: 10 property real topMargin: 10 property real valueFontSize: 21 property real labelFontSize: 10 ColumnLayout { anchors.fill: parent anchors.margins: dataPanel.topMargin spacing: dataPanel.rowSpacing Item { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: parent.width * 3/4 Layout.fillHeight: true Row { anchors.fill: parent spacing: dataPanel.columnSpacing // 今日总里程 Column { width: parent.width / 3 height: parent.height spacing: 2 Text { text: isDestroyed ? "0.00" : todayTotalDistance.toFixed(2) color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: "今日总里程(km)" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.horizontalCenter: parent.horizontalCenter } } // 本次骑行里程 Column { width: parent.width / 3 height: parent.height spacing: 2 Text { text: isDestroyed ? "0.00" : currentRideDistance.toFixed(2) color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: "本次骑行里程(km)" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.horizontalCenter: parent.horizontalCenter } } // 本次骑行时间 Column { width: parent.width / 3 height: parent.height spacing: 2 Text { text: isDestroyed ? "00:00" : formattedRidingTime color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: "本次骑行时间" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.horizontalCenter: parent.horizontalCenter } } } } Item { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: parent.width * 3/5 Layout.fillHeight: true Row { anchors.fill: parent spacing: dataPanel.columnSpacing Row { width: parent.width / 2 height: parent.height spacing: 5 anchors.verticalCenter: parent.verticalCenter Text { text: "最高海拔" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.verticalCenter: parent.verticalCenter } Text { text: isDestroyed ? "0" : maxAltitude.toFixed(0) color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.verticalCenter: parent.verticalCenter } Text { text: "m" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.verticalCenter: parent.verticalCenter } } Row { width: parent.width / 2 height: parent.height spacing: 5 anchors.verticalCenter: parent.verticalCenter Text { text: "最高车速" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.verticalCenter: parent.verticalCenter } Text { text: isDestroyed ? "0.0" : maxSpeed.toFixed(1) color: "white" font.pixelSize: dataPanel.valueFontSize font.bold: true anchors.verticalCenter: parent.verticalCenter } Text { text: "km/h" color: "#aaaaaa" font.pixelSize: dataPanel.labelFontSize anchors.verticalCenter: parent.verticalCenter } } } } } } } // 按钮覆盖层 - 只覆盖地图区域,不覆盖数据面板 Item { id: buttonsOverlay anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: mapContainer.height // 只覆盖地图区域 z: 999 // 设置为最高层级 enabled: !isDestroyed && permissionsGranted Component.onCompleted: { console.log("按钮覆盖层创建完成,尺寸:", width, "x", height) console.log("地图容器高度:", mapContainer.height) debugButtonLayout() } // 1. 左上角:速度显示按钮 Rectangle { id: speedButton width: 120 * btnSize * platformScale height: 120 * btnSize * platformScale radius: height/2 color: btnBlue x: buttonSideMargin y: buttonTopMargin visible: !isDestroyed && permissionsGranted z: 1000 // 最高优先级 // 添加强制渲染属性 layer.enabled: true layer.smooth: true Component.onCompleted: { console.log("速度按钮创建完成,位置:", x, y, "尺寸:", width, height) } Column { anchors.centerIn: parent spacing: 0 Text { text: isDestroyed ? "0.0" : Math.min(currentSpeed.toFixed(1), 99.9) color: "white" font.pixelSize: 19 * platformScale font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: "km/h" color: "white" font.pixelSize: 14 * 0.8 * platformScale anchors.horizontalCenter: parent.horizontalCenter } } } // 2. 右上角:功能按钮组 Rectangle { id: functionButtons width: 60 * platformScale height: 240 * platformScale radius: 8 color: "#CC000000" x: parent.width - width - buttonSideMargin y: buttonTopMargin visible: !isDestroyed && permissionsGranted z: 1000 Component.onCompleted: { console.log("功能按钮组创建完成,位置:", x, y, "尺寸:", width, height) } Column { anchors.fill: parent spacing: 0 FunctionButton { width: parent.width height: parent.height / 5 iconSource: "qrc:/assets/icons/discover/floating.png" tooltip: "悬浮窗功能" iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (!isDestroyed) console.log("悬浮窗功能") } } Rectangle { width: parent.width * 2 / 3 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: "#80FFFFFF" } // 暂停/继续按钮 FunctionButton { width: parent.width height: parent.height / 5 iconSource: { if (isDestroyed || !RidingDataModel) return "qrc:/assets/icons/pause.png"; return RidingDataModel.isPaused ? "qrc:/assets/icons/play.png" : "qrc:/assets/icons/pause.png"; } tooltip: { if (isDestroyed || !RidingDataModel) return "暂停"; return RidingDataModel.isPaused ? "继续" : "暂停"; } iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (isDestroyed || !RidingDataModel || !permissionsGranted) return; try { if (RidingDataModel.isRiding && !RidingDataModel.isPaused) { RidingDataModel.pauseRiding(); } else if (RidingDataModel.isRiding && RidingDataModel.isPaused) { RidingDataModel.resumeRiding(); } } catch (error) { console.error("操作骑行状态失败:", error); } } } Rectangle { width: parent.width * 2 / 3 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: "#80FFFFFF" } FunctionButton { width: parent.width height: parent.height / 5 iconSource: "qrc:/assets/icons/discover/friends.png" tooltip: "交友功能" iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (!isDestroyed) console.log("交友功能") } } Rectangle { width: parent.width * 2 / 3 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: "#80FFFFFF" } FunctionButton { width: parent.width height: parent.height / 5 iconSource: "qrc:/assets/icons/discover/vlink.png" tooltip: "已链接状态" iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (!isDestroyed) console.log("已链接状态") } } Rectangle { width: parent.width * 2 / 3 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: "#80FFFFFF" } // 分享按钮 FunctionButton { width: parent.width height: parent.height / 5 iconSource: "qrc:/assets/icons/discover/share.png" tooltip: "分享轨迹" iconWidth: parent.width - 10 iconHeight: parent.height / 5 - 20 containerPadding: 0 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true onClicked: { if (isDestroyed || !permissionsGranted) return; console.log("分享轨迹"); } } } } // 3. 回到中心按钮 Rectangle { id: centerButton width: functionButtons.width height: functionButtons.width x: parent.width - width - buttonSideMargin y: functionButtons.y + functionButtons.height + 30 radius: 8 color: "#CC000000" visible: !isDestroyed && permissionsGranted z: 1000 Component.onCompleted: { console.log("中心按钮创建完成,位置:", x, y) } FunctionButton { anchors.fill: parent iconSource: "qrc:/assets/icons/discover/location.png" iconWidth: 30 iconHeight: 30 containerPadding: (parent.width - 24) / 2 enableOpacityEffect: true enableScaleEffect: true enableColorEffect: true normalColor: "white" pressedColor: "#4d8eff" onClicked: { if (isDestroyed || !permissionsGranted) return; console.log("回到中心位置"); requestCenter = true; positionSource.active = true; } } } // 4. 左下角:记录倾角按钮 Rectangle { id: tiltButton width: 100 * btnSize * platformScale height: 100 * btnSize * platformScale radius: height/2 color: btnBlue x: buttonSideMargin y: parent.height - height - buttonSideMargin z: 1000 visible: !isDestroyed && permissionsGranted // 添加强制渲染属性 layer.enabled: true layer.smooth: true Component.onCompleted: { console.log("倾角按钮创建完成,位置:", x, y) } scale: mouseAreaTilt.pressed ? 0.95 : 1.0 opacity: mouseAreaTilt.pressed ? 0.95 : 1.0 Behavior on scale { enabled: !isDestroyed NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } } Behavior on opacity { enabled: !isDestroyed NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } } Text { text: "记录倾角" color: "white" font.pixelSize: 13 * platformScale anchors.centerIn: parent } MouseArea { id: mouseAreaTilt anchors.fill: parent onClicked: { if (!isDestroyed && permissionsGranted) console.log("记录倾角功能") } } } // 5. 右下角:打卡按钮 Rectangle { id: checkinButton width: 120 * btnSize * platformScale height: 50 * btnSize * platformScale radius: 8 color: btnBlue x: parent.width - width - buttonSideMargin y: parent.height - height - buttonSideMargin visible: !isDestroyed && permissionsGranted z: 1000 // 添加强制渲染属性 layer.enabled: true layer.smooth: true Component.onCompleted: { console.log("打卡按钮创建完成,位置:", x, y) } scale: mouseAreaCheckin.pressed ? 0.95 : 1.0 opacity: mouseAreaCheckin.pressed ? 0.95 : 1.0 Behavior on scale { enabled: !isDestroyed NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } } Behavior on opacity { enabled: !isDestroyed NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } } Text { text: { if (isDestroyed || !RidingDataModel) return "开始记录"; if (!RidingDataModel.isRiding) return "开始记录"; return RidingDataModel.isPaused ? "继续记录" : "停止记录"; } color: "white" font.pixelSize: 16 * platformScale anchors.centerIn: parent } MouseArea { id: mouseAreaCheckin anchors.fill: parent onClicked: { if (isDestroyed || !RidingDataModel) { console.error("RidingDataModel 未定义"); return; } if (!permissionsGranted) { showPermissionDialog("权限提示", "请先授予定位权限以使用骑行记录功能"); requestLocationPermissions(); return; } if (!checkLocationPermission()) { return; } try { if (!RidingDataModel.isRiding) { clearMapTrack(); RidingDataModel.startRiding(); } else { if (RidingDataModel.isPaused) { RidingDataModel.resumeRiding(); } else { RidingDataModel.stopRiding(); } } } catch (error) { console.error("操作骑行状态失败:", error); } } onPressAndHold: { if (isDestroyed || !RidingDataModel || !permissionsGranted) return; if (RidingDataModel.isRiding && !RidingDataModel.isPaused) { try { RidingDataModel.pauseRiding(); } catch (error) { console.error("暂停骑行失败:", error); } } } } } } } // 添加错误显示函数 function showMapLoadError(errorMsg) { console.error("地图加载错误:", errorMsg); var errorRect = Qt.createQmlObject(` import QtQuick 2.15 import QtQuick.Controls 2.15 Rectangle { id: errorOverlay anchors.fill: parent color: "#2C3E50" Column { anchors.centerIn: parent spacing: 20 Text { text: "地图加载失败" color: "white" font.pixelSize: 18 font.bold: true } Text { text: "错误信息: ${errorMsg}" color: "#E74C3C" font.pixelSize: 14 width: parent.width * 0.8 wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter } Text { text: "可能的原因:\\n• 网络连接问题\\n• 高德地图API密钥问题\\n• 跨域访问限制" color: "#CCCCCC" font.pixelSize: 12 width: parent.width * 0.8 wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter } Button { text: "重新加载" onClicked: { webView.reload() } } } } `, webView.parent, "errorOverlay"); } } 』」
最新发布
10-29
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值