(* cascade_height=<number> *)

       在Verilog/SystemVerilog中,(* cascade_height = <number> *) 是一个综合优化属性,主要用于控制LUT(查找表)级联的高度,特别是在FPGA设计中优化逻辑链的结构。

一、语法和作用

(* cascade_height = 4 *) wire chain_signal;
(* cascade_height = 8 *) reg cascade_var;

二、主要用途

1. 控制LUT级联链长度

module cascade_example (
    input wire [7:0] a, b, c, d,
    output wire result
);
    
    (* cascade_height = 4 *) wire cascade_chain;
    
    // 创建级联逻辑链
    assign cascade_chain = a[0] & a[1] & a[2] & a[3] & 
                          b[0] & b[1] & b[2] & b[3] &
                          c[0] & c[1] & c[2] & c[3] &
                          d[0] & d[1] & d[2] & d[3];
    
    assign result = cascade_chain;
endmodule

2. 优化宽输入逻辑函数

module wide_and_function (
    input wire [15:0] inputs,
    output wire all_ones
);
    
    (* cascade_height = 3 *) wire and_chain;
    
    // 16输入AND函数,通过级联实现
    assign and_chain = &inputs;  // 等同于 inputs[0] & inputs[1] & ... & inputs[15]
    
    assign all_ones = and_chain;
endmodule

3. 比较器链优化

module priority_encoder (
    input wire [7:0] req,
    output reg [2:0] grant
);
    
    (* cascade_height = 2 *) wire [3:0] priority_chain;
    
    // 优先级编码的级联逻辑
    assign priority_chain[0] = req[0];
    assign priority_chain[1] = req[1] & ~req[0];
    assign priority_chain[2] = req[2] & ~req[1] & ~req[0];
    assign priority_chain[3] = req[3] & ~req[2] & ~req[1] & ~req[0];
    
    always @(*) begin
        case (1'b1)
            priority_chain[0]: grant = 3'b000;
            priority_chain[1]: grant = 3'b001;
            priority_chain[2]: grant = 3'b010;
            priority_chain[3]: grant = 3'b011;
            req[4]: grant = 3'b100;
            req[5]: grant = 3'b101;
            req[6]: grant = 3'b110;
            req[7]: grant = 3'b111;
            default: grant = 3'b000;
        endcase
    end
endmodule

4. 算术运算优化

module carry_chain_optimization (
    input wire [7:0] a, b,
    input wire cin,
    output wire [7:0] sum,
    output wire cout
);
    
    (* cascade_height = 2 *) wire [3:0] carry_chain;
    
    // 使用级联优化的进位链
    assign {carry_chain[0], sum[0]} = a[0] + b[0] + cin;
    assign {carry_chain[1], sum[1]} = a[1] + b[1] + carry_chain[0];
    assign {carry_chain[2], sum[2]} = a[2] + b[2] + carry_chain[1];
    assign {carry_chain[3], sum[3]} = a[3] + b[3] + carry_chain[2];
    
    // 继续其他位...
    assign {cout, sum[7:4]} = a[7:4] + b[7:4] + carry_chain[3];
endmodule

5. 移位寄存器链

module shift_register_cascade (
    input wire clk,
    input wire rst,
    input wire serial_in,
    output wire serial_out
);
    
    (* cascade_height = 4 *) reg [15:0] shift_reg;
    
    always @(posedge clk) begin
        if (rst)
            shift_reg <= 16'h0000;
        else
            shift_reg <= {shift_reg[14:0], serial_in};
    end
    
    assign serial_out = shift_reg[15];
endmodule

三、工具支持

  • Xilinx Vivado: 支持(主要用于FPGA LUT级联优化)

  • Intel Quartus: 支持(可能有不同的属性名称)

  • Lattice Diamond: 支持

  • 其他FPGA工具: 通常都支持级联优化属性

四、类似和相关属性

(* use_carry_chain = "yes" *)        // 使用专用进位链
(* cascade_order = "forward" *)      // 级联顺序
(* lut_cascade = "true" *)           // LUT级联使能

五、使用建议

1. 合理设置级联高度

(* cascade_height = 2 *) wire fast_chain;    // 低延迟,高频率
(* cascade_height = 8 *) wire area_chain;    // 节省面积,较低频率

2. 性能权衡

module performance_tradeoff (
    input wire [31:0] data,
    output wire parity
);
    
    // 选项1:高层级联 - 面积优化
    (* cascade_height = 8 *) wire parity_high;
    assign parity_high = ^data;  // 奇偶校验
    
    // 选项2:低层级联 - 性能优化  
    (* cascade_height = 2 *) wire parity_low;
    assign parity_low = ^data;
    
    assign parity = parity_high;  // 根据需求选择
endmodule

六、典型应用场景

  1. 宽输入逻辑函数(AND、OR、XOR等)

  2. 进位链优化(加法器、计数器)

  3. 比较器链(优先级编码、仲裁器)

  4. 移位寄存器

  5. 乘法器部分积

七、注意事项

  1. 工具依赖性 - 不同FPGA厂商的工具可能有不同的实现

  2. 时序影响 - 增加级联高度可能降低最大频率

  3. 资源使用 - 优化级联结构可以节省LUT资源

  4. 验证必要 - 需要检查综合后的时序报告

这个属性在FPGA设计中特别有用,可以帮助工程师在时序和面积之间做出更好的权衡。

module distance_gate_processor ( input clk, // 250MHz时钟 input rst, // 复位信号(高有效) input data_valid, // 输入数据有效标志 input [15:0] data_in, // 输入数据 output reg [15:0] data_out [0:49], // 输出寄存器数组 output reg frame_done // 帧完成信号 ); // 参数定义 parameter NUM_GATES = 50; // 距离门数量 parameter REAL_POINTS = 500; // 真实数据点数 parameter ZERO_PAD = 12; // 补零点数 parameter TOTAL_POINTS = REAL_POINTS + ZERO_PAD; // 总点数/门 // 控制信号 reg [5:0] gate_index; // 距离门索引 (0-49) reg [8:0] point_index; // 点索引 (0-511) wire write_enable; // ==== BRAM自动级联实现 ==== (* cascade_height = 4, ram_style = "block" *) reg [15:0] ram [0:NUM_GATES*TOTAL_POINTS-1]; // 50×512深度×16宽度 // 输出控制信号 typedef enum {IDLE, OUTPUT} state_t; reg [5:0] out_gate; // 输出门索引 reg [8:0] out_point; // 输出点索引 state_t output_state; reg output_ready; // 输出数据有效标志 // 地址计算 wire [14:0] write_addr = gate_index * TOTAL_POINTS + point_index; wire [14:0] read_addr = out_gate * TOTAL_POINTS + out_point; // 写使能:仅在有效数据周期或补零周期使能 assign write_enable = data_valid || (point_index >= REAL_POINTS); // 初始化逻辑 initial begin for (int i = 0; i < NUM_GATES*TOTAL_POINTS; i++) begin ram[i] = 16'b0; end for (int g = 0; g < NUM_GATES; g++) begin data_out[g] = 16'b0; end end // ==== 写控制逻辑 ==== always @(posedge clk or posedge rst) begin if (rst) begin gate_index <= 0; point_index <= 0; frame_done <= 0; end else begin frame_done <= 0; if (write_enable) begin // 真实数据阶段 (0-499) if (point_index < REAL_POINTS) begin ram[write_addr] <= data_in; end // 自动触发补零 (500-511) else begin ram[write_addr] <= 16'b0; // 显式补零 end // 索引更新 if (point_index == TOTAL_POINTS-1) begin point_index <= 0; if (gate_index == NUM_GATES-1) begin gate_index <= 0; end else begin gate_index <= gate_index + 1; end end else begin point_index <= point_index + 1; end end end end // ==== 输出状态机逻辑 ==== always @(posedge clk or posedge rst) begin if (rst) begin output_state <= IDLE; out_gate <= 0; out_point <= 0; output_ready <= 0; frame_done <= 0; // 初始化输出数组 for (int g = 0; g < NUM_GATES; g++) data_out[g] <= 16'b0; end else begin output_ready <= 0; // 默认无效 case(output_state) IDLE: begin // 检测所有门数据收集完成 if (gate_index == NUM_GATES-1 && point_index == TOTAL_POINTS-1 && write_enable) begin output_state <= OUTPUT; out_gate <= 0; out_point <= 0; end end OUTPUT: begin // 更新当前门的数据 (自动级联读取) data_out[out_gate] <= ram[read_addr]; output_ready <= 1; // 索引更新 if (out_point == TOTAL_POINTS-1) begin out_point <= 0; if (out_gate == NUM_GATES-1) begin out_gate <= 0; output_state <= IDLE; frame_done <= 1; // 帧完成信号 end else begin out_gate <= out_gate + 1; end end else begin out_point <= out_point + 1; end end endcase end end // ==== 时序优化寄存器 ==== (* use_dsp48 = "no" *) (* dont_touch = "true" *) reg [5:0] gate_index_reg; reg [8:0] point_index_reg; always @(posedge clk) begin gate_index_reg <= gate_index; point_index_reg <= point_index; end endmodule .vivado报错[Synth 8-3391] Unable to infer a block/distributed RAM for 'ram_reg' because the memory pattern used is not supported. Failed to dissolve the memory into bits because the number of bits (409600) is too large. Use 'set_param synth.elaboration.rodinMoreOptions {rt::set_parameter dissolveMemorySizeLimit 409600}' to allow the memory to be dissolved into individual bits 我的芯片是xc7z045ffg900
09-22
根据页面代码帮我设计mysql表和java接口:<template> <div class="app-container"> <!-- 搜索表单 --> <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px"> <el-form-item label="客户名称" prop="customerName"> <input type="text" v-model="queryParams.customerName" placeholder="请输入客户名称" clearable size="small" @keyup.enter.native="handleQuery"/> </el-form-item> <el-form-item label="项目名称" prop="projectName"> <input type="text" v-model="queryParams.projectName" placeholder="请输入项目名称" clearable size="small" @keyup.enter.native="handleQuery"/> </el-form-item> <el-form-item label="成品料号" prop="batteryFinishedPartNumber"> <input type="text" v-model="queryParams.batteryFinishedPartNumber" placeholder="请输入成品料号" clearable size="small" @keyup.enter.native="handleQuery"/> </el-form-item> <el-form-item label="半成品料号" prop="pcbSemiFinishedPartNumber"> <input type="text" v-model="queryParams.pcbSemiFinishedPartNumber" placeholder="请输入半成品料号" clearable size="small" @keyup.enter.native="handleQuery"/> </el-form-item> <el-form-item label="丝印" prop="mainBoardSilkScreen"> <input type="text" v-model="queryParams.mainBoardSilkScreen" placeholder="请输入丝印" clearable size="small" @keyup.enter.native="handleQuery"/> </el-form-item> <el-form-item label="文档版次" prop="revision"> <input type="text" v-model="queryParams.revision" placeholder="请输入文档版次" clearable size="small" @keyup.enter.native="handleQuery"/> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> </el-form-item> </el-form> <!-- 操作按钮 --> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['cms:productionspecificationdata:add']">新增</el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['cms:productionspecificationdata:remove']">批量删除</el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['cms:productionspecificationdata:export']">批量导出PDF</el-button> </el-col> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"/> </el-row> <!-- 表格 --> <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="30" align="center"/> <el-table-column label="ID" fixed="left" width="50" prop="id" align="center"/> <el-table-column label="项目名称" fixed="left" prop="projectName" align="center" sortable width="130"/> <el-table-column label="电芯品牌" prop="cellBrand" align="center" width="130"/> <el-table-column label="电芯型号" prop="cellModel" align="center"/> <el-table-column label="丝印" prop="mainBoardSilkScreen" align="center" width="130"/> <el-table-column label="料号" prop="mainBoardPartNumber" align="center" width="100"/> <el-table-column label="客户名称" prop="customerName" align="center"/> <el-table-column label="文档版次" prop="revision" align="center"/> <el-table-column label="更新人" prop="updateBy" align="center" width="100" sortable/> <el-table-column label="更新时间" prop="updateTime" align="center" width="100" sortable> <template slot-scope="scope"> <span>{{ parseTime(scope.row.updateTime || scope.row.createTime) }}</span> </template> </el-table-column> <el-table-column label="PCB顶图" align="center" width="150"> <template slot-scope="scope"> <el-button v-if="scope.row.pcbPlacementDiagramTopImg" type="text" icon="el-icon-document" @click="previewFile(scope.row.pcbPlacementDiagramTopImg)">查看顶图</el-button> <span v-else>—</span> </template> </el-table-column> <el-table-column label="PCB底图" align="center" width="150"> <template slot-scope="scope"> <el-button v-if="scope.row.pcbPlacementDiagramBottomImg" type="text" icon="el-icon-document" @click="previewFile(scope.row.pcbPlacementDiagramBottomImg)">查看底图</el-button> <span v-else>—</span> </template> </el-table-column> <el-table-column label="PCM端口示意图" align="center" width="150"> <template slot-scope="scope"> <el-button v-if="scope.row.pcbPlacementDiagramBottomImg" type="text" icon="el-icon-document" @click="previewFile(scope.row.pcbPlacementDiagramBottomImg)">查看底图</el-button> <span v-else>—</span> </template> </el-table-column> <el-table-column label="电池组输出端口图" align="center" width="150"> <template slot-scope="scope"> <el-button v-if="scope.row.pcbPlacementDiagramBottomImg" type="text" icon="el-icon-document" @click="previewFile(scope.row.pcbPlacementDiagramBottomImg)">查看底图</el-button> <span v-else>—</span> </template> </el-table-column> <el-table-column label="NTC阻值R-T表" align="center" width="150"> <template slot-scope="scope"> <el-button v-if="scope.row.pcbPlacementDiagramBottomImg" type="text" icon="el-icon-document" @click="previewFile(scope.row.pcbPlacementDiagramBottomImg)">查看底图</el-button> <span v-else>—</span> </template> </el-table-column> <el-table-column label="操作" align="center" fixed="right" width="160" class-name="small-padding fixed-width"> <template slot-scope="scope"> <el-button size="mini" type="text" icon="el-icon-download" @click="handleExportPDF(scope.row)" v-hasPermi="['cms:productionspecificationdata:export']" >下载PDF</el-button> <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['cms:productionspecificationdata:edit']" >修改</el-button> <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['cms:productionspecificationdata:remove']" >删除</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/> <!-- 添加/编辑对话框 --> <el-dialog :title="title" :visible.sync="open" :before-close="cancel" :close-on-click-modal="false" width="95%" top="5vh" append-to-body > <el-tabs type="border-card"> <h1 style="text-align: center; margin-bottom: 100px;">生产规格书</h1> <!-- 基本信息 --> <el-row :gutter="20"> <el-col :span="6"> <div class="grid-content"> <label class="label-cell">客户名称:</label> <input type="text" v-model="specForm.customerName" /> </div> </el-col> <el-col :span="6"> <div class="grid-content"> <label class="label-cell">项目名称:</label> <input type="text" v-model="specForm.projectName" /> </div> </el-col> <el-col :span="6"> <div class="grid-content"> <label class="label-cell">成品料号:</label> <input type="text" v-model="specForm.batteryFinishedPartNumber" /> </div> </el-col> <el-col :span="6"> <div class="grid-content"> <label class="label-cell">半成品料号:</label> <input type="text" v-model="specForm.pcbSemiFinishedPartNumber" /> </div> </el-col> </el-row> <!-- 保密等级 --> <h4>保密等级:</h4> <div style="text-align: center; margin: 20px;"> <label> <input type="radio" disabled> 绝密 </label>    <label> <input type="radio" disabled> 机密 </label>    <label> <input type="radio" checked disabled> 内部公开 </label>    <label> <input type="radio" disabled> 外部公开 </label> </div> <!-- 审批信息 --> <h4>审批信息</h4> <table class="spec-table"> <tr> <th>拟制/日期<br>电子工程师</th> <th>审核/日期<br>直接上级</th> <th>审核/日期<br>DQE</th> <th>审核/日期<br>NPI</th> <th>批准/日期<br>经理</th> </tr> <tr> <td></td> <td></td> <td></td> <td></td> <td></td> </tr> </table> <!-- 文件修正记录 --> <h4>文件修正记录</h4> <div id="app"> <table class="spec-table"> <thead> <tr> <th>序号</th> <th>版次</th> <th>变更日期</th> <th>变更内容</th> <th>拟制</th> <th>审核</th> <th>批准</th> </tr> </thead> <tbody> <tr v-for="(item, index) in versionRecordList" :key="item.id"> <td>{{ index + 1 }}</td> <td><input type="text" v-model="item.revision" /></td> <td><input type="text" v-model="item.updateTime" /></td> <td><input type="text" v-model="item.changeDescription" /></td> <td><input type="text" v-model="item.updateBy" /></td> <td><input type="text" v-model="item.reviewedBy" /></td> <td><input type="text" v-model="item.approvedBy" /></td> </tr> </tbody> </table> <div style="text-align: center"> <button @click="addRow">+</button> </div> </div> <hr class="divider"> <!-- 目录 --> <div class="directory-container"> <h2>目录</h2> <ul class="spec-directory"> <li>1 引言</li> <li>1.1 编写目的</li> <li>1.2 适用范围</li> <li>2 版本说明</li> <li>2.1 硬件版本</li> <li>2.2 软件版本</li> <li>2.3 上位机软件版本</li> <li>3 PCB贴片图</li> <li>4 PCM测试及检验规范</li> <li>4.1 PCM端口示意图</li> <li>4.2 烧录</li> <li>4.3 PCM测试</li> <li>4.4 PCBA半成品工艺要求</li> <li>5 半成品测试及检验规范</li> <li>5.1 电芯配组要求</li> <li>5.2 PCM组装上电顺序</li> <li>5.3 输出端口定义</li> <li>5.4 功能测试及检验标准</li> <li>6 成品测试及检验规范</li> <li>6.1 成品检测项目及标准</li> <li>6.2 成品结构尺寸</li> <li>6.3 成品打码规则</li> <li>6.4 追溯要求</li> <li>附录一</li> <li>附录二</li> </ul> </div> <hr class="divider"> <!-- 引言 --> <h3>1 引言</h3> <p>本文主要描述该项目产品的生产性能要求说明,明确整个产品在PCM、半成品、成品的性能测试标准及要求。</p> <h4>1.1 编写目的</h4> <p>本文档描述了本产品的生产过程性能测试要求及标准、工艺要求等。方便生产相关人员对本产品的制造和检验。</p> <h4>1.2 适用范围</h4> <p>本规格书适用于欣动能源科技有限公司设计的产品生产技术规范,适用于与之相关的生产和检验。</p> <hr class="divider"> <!-- 版本说明 --> <h4>2 版本说明</h4> <h5>2.1 硬件版本</h5> <table class="spec-table"> <thead> <tr> <th>序号</th> <th>PCB</th> <th>丝印</th> <th>料号</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>主板</td> <td><input type="text" v-model="specForm.mainBoardSilkScreen" /></td> <td><input type="text" v-model="specForm.mainBoardPartNumber" /></td> <td></td> </tr> <tr> <td>2</td> <td>采集板</td> <td>/</td> <td>/</td> <td></td> </tr> <tr> <td>3</td> <td>灯板</td> <td>/</td> <td>/</td> <td></td> </tr> </tbody> </table> <h5>2.2 软件版本</h5> <table class="spec-table"> <thead> <tr> <th>序号</th> <th>PCM</th> <th>软件</th> <th>版本信息</th> <th>备注</th> </tr> </thead> <tbody> <tr><td>1</td><td rowspan="10">主板</td><td>Bootloader</td><td>/</td><td></td></tr> <tr><td>2</td><td>应用程序</td><td>/</td><td></td></tr> <tr><td>3</td><td>一体程序</td><td>/</td><td></td></tr> <tr><td rowspan="3">4</td><td rowspan="3">固件信息</td> <td>/</td><td rowspan="3"></td> </tr> <tr><td>/</td></tr> <tr><td>/</td></tr> <tr><td>5</td><td>烧录芯片选型</td><td>/</td><td></td></tr> <tr><td>6</td><td>固件烧录软件</td><td>/</td><td></td></tr> <tr><td>7</td><td>通讯设备</td><td>/</td><td></td></tr> </tbody> </table> <h5>2.3 上位机软件版本</h5> <h5 style="text-align: center;">生产上位机</h5> <table class="spec-table"> <thead> <tr> <th>序号</th> <th>上位机</th> <th>版本</th> <th>备注</th> </tr> </thead> <tbody> <tr><td>1</td><td>Bin对比上位机</td><td>/</td><td></td></tr> <tr><td>2</td><td>通讯/升级上位机</td><td>/</td><td></td></tr> <tr><td>3</td><td>自动对比上位机</td><td>/</td><td></td></tr> <tr><td>4</td><td>通信设备</td><td>/</td><td></td></tr> </tbody> </table> <h5 style="text-align: center;">生产文件</h5> <table class="spec-table"> <thead> <tr> <th>序号</th> <th>上位机</th> <th>版本</th> <th>备注</th> </tr> </thead> <tbody> <tr><td>1</td><td>Bin对比上位机</td><td>/</td><td></td></tr> <tr><td>2</td><td>通讯/升级上位机模板</td><td>/</td><td></td></tr> <tr><td>3</td><td>PCM自动对比模板</td><td>/</td><td></td></tr> <tr><td>4</td><td>半成品自动对比模板</td><td>/</td><td></td></tr> <tr><td>4</td><td>成品自动对比模板</td><td>/</td><td></td></tr> </tbody> </table> <!-- PCB贴片图 --> <hr class="divider"> <h4>3 PCB贴片图</h4> <div> <label>顶面</label> <input type="file" @change="uploadPcbTopImage" /> <label>底面</label> <input type="file" @change="uploadPcbBottomImage" /> </div> <!-- PCM测试及检验规范 --> <hr class="divider"> <h4>4 PCM测试及检验规范</h4> <h5>4.1 PCM端口示意图</h5> <input type="file" @change="uploadPcmPortImage" /> <hr class="divider"> <h5>4.2 烧录</h5> <p>不涉及</p> <hr class="divider"> <h5>4.3 PCM测试</h5> <table class="spec-table"> <thead> <tr> <th colspan="2">检验项目</th> <th>性能要求</th> <th>必检项</th> <th>抽检项</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td rowspan="16">充电保护</td> <td>过充电保护电压</td> <td>4.175±0.025V</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>每节电芯</td> </tr> <tr> <td>过充电保护延时</td> <td>1.0±0.5S</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>过充电保护释放电压</td> <td>4.075±0.025V</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>每节电芯</td> </tr> <tr> <td>过充电保护电压2</td> <td>4.25±0.025V</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>每节电芯</td> </tr> <tr> <td>过充电保护延时2</td> <td>1.0±0.5S</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>过放电保护电压</td> <td>2.80±0.03V</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>每节电芯</td> </tr> <tr> <td>过放电保护延时</td> <td>1.0±0.5 S</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>过放电保护释放电压</td> <td>3.0±0.03V</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>每节电芯</td> </tr> <tr> <td>充电过流保护</td> <td>6.67±1.67A</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>充电过电流保护延时</td> <td>500±200ms</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>放电过流保护1</td> <td>16.67±2A</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>放电过电流保护1延时</td> <td>200±100ms</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>放电过流保护2</td> <td>33.2±4A</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>放电过电流保护2延时</td> <td>20±10ms</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>短路保护</td> <td>66±7A</td> <td><input type="checkbox" disabled/></td> <td><input type="checkbox" checked disabled /></td> <td></td> </tr> <tr> <td>短路保护延时</td> <td><1000us</td> <td><input type="checkbox" disabled/></td> <td><input type="checkbox" checked disabled /></td> <td></td> </tr> <tr> <td rowspan="2">充放电</td> <td>充电测试</td> <td>16.6V/5.3A@5S</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>单边测试法满足5.3A@5S即可</td> </tr> <tr> <td>正常放电</td> <td>1A@2S</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td rowspan="4">阻值测试</td> <td>NTC1阻值测试</td> <td>当前电池温度在附录一R-T表中所对应的阻值在该温度±3℃所对应的阻值范围内</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>测试方法:检测测试点 RT1和B-1间阻值,并判断其是否在当前电池温度±3℃所对应的阻值范围内。生产时需检测并控制环境温度,按附录一 R-T表灵活调整阻值范围。(例,电池温度为25℃时,对应阻值范围为8.8KΩ~11.4KΩ)</td> </tr> <tr> <td>B-与P-之间阻值</td> <td>≤40mΩ</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>不包括外部测试设备等阻值</td> </tr> <tr> <td>B+与P+之间阻值</td> <td>≤20mΩ</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>不包括外部测试设备等阻值</td> </tr> <tr> <td>NTC与P-端子间阻值</td> <td>当前电池温度在附录二R-T表中所对应的阻值在该温度±3℃所对应的阻值范围内</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>NTC和P-端子之间阻值,需要在在当前电池温度±3℃所对应的阻值范围内。参考附录二R-T表灵活调整阻值范围。(例,电池温度为 25℃时,对应阻值范围为8.8KΩ ~11.3KΩ)</td> </tr> <tr> <td rowspan="2">功耗</td> <td>工作功耗</td> <td>≤40uA</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>工作模式</td> </tr> <tr> <td>休眠功耗</td> <td>≤15uA</td> <td><input type="checkbox" disabled/></td> <td><input type="checkbox" checked disabled /></td> <td>设备欠压保护32±8S后进入休眠模式</td> </tr> </tbody> </table> <h5>4.4 PCBA半成品工艺要求</h5> <div> <label>参见受控的 PCBA 半成品检验标准</label> </div> <h4>5 半成品测试及检验规范</h4> <h5>5.1 电芯配组要求</h5> <table class="spec-table"> <thead> <tr> <th>序号</th> <th>检查项</th> <th>要求</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>电芯型号</td> <td><input type="text" v-model="specForm.cellModel" /></td> <td><input type="text" v-model="specForm.cellBrand" /></td> </tr> <tr> <td>2</td> <td>内阻差</td> <td> <input type="text" v-model="specForm.maxCellInternalResistanceMohm" /> </td> <td>单个电芯内阻≤<input type="text" v-model="specForm.maxCellGroupResistanceDiffMohm" style="display: inline-block; width: 200px;" /></td> </tr> <tr> <td>3</td> <td>电压差</td> <td> <input type="text" v-model="specForm.maxCellGroupVoltageDiffMv" /> </td> <td>根据电芯来料检验标准</td> </tr> <tr> <td>4</td> <td>容量差</td> <td> <input type="text" v-model="specForm.maxCellGroupCapacityDiffMah" /> </td> <td>容量差由电芯供应商管控</td> </tr> <tr> <td>5</td> <td>档位/批次</td> <td>同一组电芯组为同一批次电芯,容量档位不能混用</td> <td></td> </tr> </tbody> </table> <h5>5.2 PCM组装上电顺序</h5> <div><label>1)电芯上电顺序:<input type="text" v-model="specForm.cellPowerOnSequence" style="display: inline-block; width: 250px;" />;</label></div> <div><label>2)组装成半成品后,在半成品测试之前需焊接JP点。</label></div> <div><label>注:上电后不允许在PCM上焊接任何接线或端子</label></div> <h5>5.3 输出端口定义</h5> <table class="spec-table"> <thead> <tr> <th>Terminal<br>端子</th> <th>Name<br>名称</th> <th>Description<br>描述</th> </tr> </thead> <tbody> <tr> <td>Pin1</td> <td>P+</td> <td>UL1007 20AWG 红</td> </tr> <tr> <td>Pin2</td> <td>P+</td> <td>UL1007 20AWG 红</td> </tr> <tr> <td>Pin3</td> <td>NTC</td> <td>UL1007 24AWG 白</td> </tr> <tr> <td>Pin4</td> <td>P-</td> <td>UL1007 20AWG 黑</td> </tr> <tr> <td>Pin5</td> <td>P-</td> <td>UL1007 20AWG 黑</td> </tr> <tr> <th colspan="3">Battery pack output port diagram/电池组输出端口图</th> </tr> <tr> <td colspan="3"> <input type="file" @change="uploadBatteryPackOutputPortDiagramImg" /></td> </tr> </tbody> </table> <h5>5.4 功能测试及检验标准</h5> <table class="spec-table"> <thead> <tr> <th colspan="2">检验项目</th> <th>性能要求</th> <th>必检项</th> <th>抽检项</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td rowspan="14">功能测试</td> <td>充电过流保护</td> <td><input type="text" v-model="specForm.chargeOvercurrentProtection" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox1 === true" @click="specForm.checkbox1 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox1 === false" @click="specForm.checkbox1 = false" /> </td> <td></td> </tr> <tr> <td>充电过电流保护延时</td> <td><input type="text" v-model="specForm.chargeOvercurrentDelay" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox2 === true" @click="specForm.checkbox2 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox2 === false" @click="specForm.checkbox2 = false" /> </td> <td></td> </tr> <tr> <td>放电过流保护1</td> <td><input type="text" v-model="specForm.level1DischargeOvercurrentProtection" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox3 === true" @click="specForm.checkbox3 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox3 === false" @click="specForm.checkbox3 = false" /> </td> <td></td> </tr> <tr> <td>放电过电流保护1延时</td> <td><input type="text" v-model="specForm.level1DischargeOvercurrentDelay" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox4 === true" @click="specForm.checkbox4 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox4 === false" @click="specForm.checkbox4 = false" /> </td> <td></td> </tr> <tr> <td>放电过流保护2</td> <td><input type="text" v-model="specForm.level2DischargeOvercurrentProtection" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox5 === true" @click="specForm.checkbox5 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox5 === false" @click="specForm.checkbox5 = false" /> </td> <td></td> </tr> <tr> <td>放电过电流保护2延时</td> <td><input type="text" v-model="specForm.level2DischargeOvercurrentDelay" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox6 === true" @click="specForm.checkbox6 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox6 === false" @click="specForm.checkbox6 = false" /> </td> <td></td> </tr> <tr> <td>短路保护</td> <td><input type="text" v-model="specForm.shortCircuitProtection" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox7 === true" @click="specForm.checkbox7 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox7 === false" @click="specForm.checkbox7 = false" /> </td> <td></td> </tr> <tr> <td>短路保护延时</td> <td><input type="text" v-model="specForm.shortCircuitDelay" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox8 === true" @click="specForm.checkbox8 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox8 === false" @click="specForm.checkbox8 = false" /> </td> <td></td> </tr> <tr> <td>充电测试</td> <td><input type="text" v-model="specForm.normalCharging" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox9 === true" @click="specForm.checkbox9 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox9 === false" @click="specForm.checkbox9 = false" /> </td> <td></td> </tr> <tr> <td>正常放电</td> <td><input type="text" v-model="specForm.normalDischarging" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox10 === true" @click="specForm.checkbox10 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox10 === false" @click="specForm.checkbox10 = false" /> </td> <td></td> </tr> <tr> <td>开路电压</td> <td><input type="text" v-model="specForm.openCircuitVoltage" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox11 === true" @click="specForm.checkbox11 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox11 === false" @click="specForm.checkbox11 = false" /> </td> <td>单节3.6-3.7V</td> </tr> <tr> <td>PACK 内阻</td> <td><input type="text" v-model="specForm.packInternalResistanceMohm" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox12 === true" @click="specForm.checkbox12 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox12 === false" @click="specForm.checkbox12 = false" /> </td> <td>不包括测试线材阻值</td> </tr> <tr> <td>JP点连通性测试</td> <td><input type="text" v-model="specForm.jPpointContinuityTest" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox13 === true" @click="specForm.checkbox13 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox13 === false" @click="specForm.checkbox13 = false" /> </td> <td>测试点TD与B-之间电压</td> </tr> <tr> <td>NTC与P-端子间阻值</td> <td>当前电池温度在附录二R-T表中所对应的阻值在该温度±3℃所对应的阻值范围内</td> <td> <input type="checkbox" :checked="specForm.checkbox14 === true" @click="specForm.checkbox14 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox14 === false" @click="specForm.checkbox14 = false" /> </td> <td>NTC和P-端子之间阻值,需要在在当前电池温度±3℃所对应的阻值范围内...</td> </tr> <tr> <td rowspan="5">全程活化</td> <td>CC-CV充电</td> <td colspan="3"><input type="text" v-model="specForm.fullCcCvCharging" style="width: 250px;" /></td> <td rowspan="7">试产阶段需全程活化,量产后可由团队评估是否简易活化</td> </tr> <tr> <td>搁置</td> <td colspan="3"><input type="text" v-model="specForm.fullStandby" style="width: 250px;" /></td> </tr> <tr> <td>恒流放电</td> <td colspan="3"><input type="text" v-model="specForm.fullConstantCurrentDischarge" style="width: 250px;" /></td> </tr> <tr> <td>搁置</td> <td colspan="3"><input type="text" v-model="specForm.fullStandby" style="width: 250px;" /></td> </tr> <tr> <td>充电至出货电压</td> <td colspan="3"><input type="text" v-model="specForm.fullChargeToShipmentVoltage" style="width: 250px;" /></td> </tr> <tr> <td rowspan="2">简易活化</td> <td>充电至出货电压</td> <td colspan="3"><input type="text" v-model="specForm.quickChargeToShipmentVoltage" style="width: 250px;" /></td> </tr> <tr> <td>搁置</td> <td colspan="3"><input type="text" v-model="specForm.quickStandby" style="width: 250px;" /></td> </tr> <tr> <td>活化标准</td> <td>放电容量</td> <td><input type="text" v-model="specForm.standardDischargeCapacity" style="width: 250px;" /></td> <td> <input type="checkbox" :checked="specForm.checkbox15 === true" @click="specForm.checkbox15 = true" /> </td> <td> <input type="checkbox" :checked="specForm.checkbox15 === false" @click="specForm.checkbox15 = false" /> </td> <td>全程活化放电容量</td> </tr> </tbody> </table> <div> 备注: <div style="padding-left: 2em;"> 1. 如果半成品测试项和成品测试项相同,团队可以根据不良率评估是否进行删减; </div> <div style="padding-left: 2em;"> 2. 试产阶段需全部活化,是否快速活化由团队决定。 </div> </div> <h4>6 成品测试及检验规范</h4> <h5>6.1 成品检测项目及标准</h5> <table class="spec-table"> <thead> <tr> <th colspan="2">检验项目</th> <th>性能要求</th> <th>测试类型</th> <th>必检项</th> <th>抽检项</th> <th >备注</th> </tr> </thead> <tbody> <!-- 保护测试 --> <tr> <td rowspan="8">保护测试</td> <td>充电过流保护</td> <td>6.67±1.67A</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>充电过电流保护延时</td> <td>500±200ms</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>放电过流保护1</td> <td>16.67±2A</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>放电过电流保护1延时</td> <td>200±100ms</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>放电过流保护2</td> <td>33.2±4A</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>放电过电流保护2延时</td> <td>20±10ms</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>短路保护</td> <td>66±7A</td> <td><input type="checkbox" disabled /></td> <td><input type="checkbox" checked disabled/></td> <td></td> </tr> <tr> <td>短路保护延时</td> <td><1000us</td> <td><input type="checkbox" disabled /></td> <td><input type="checkbox" checked disabled/></td> <td></td> </tr> <!-- 功能测试 --> <tr> <td rowspan="2">功能测试</td> <td>充电测试</td> <td>16.6V/5.3A@5S</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <tr> <td>正常放电</td> <td>1A@2S</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> <!-- 阻值测试 --> <tr> <td rowspan="2">阻值测试</td> <td>Pack内阻</td> <td><120mΩ</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>不包括测试线材阻值</td> </tr> <tr> <td>NTC与P-端子间阻值</td> <td>当前电池温度在附录二R-T表中所对应的阻值在该温度±3℃所对应的阻值范围内</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td>NTC和P-端子之间阻值,需要在在当前电池温度±3℃所对应的阻值范围内。参考附录二R-T表灵活调整阻值范围。(例,电池温度为 25℃时,对应阻值范围为8.8KΩ ~11.3KΩ)</td> </tr> <!-- 出货电压 --> <tr> <td colspan="2">出货电压</td> <td>14.4~15.2V</td> <td><input type="checkbox" checked disabled /></td> <td><input type="checkbox" disabled/></td> <td></td> </tr> </tbody> </table> <!-- 备注 --> <div style="margin: 10px 0; font-style: italic;"> 注:对于成测与半测相同项,量产后,可根据实际情况进行分工测试。 </div> <!-- 成品结构尺寸 --> <h5>6.2 成品结构尺寸</h5> <p>参见受控的成品检验标准文件</p> <!-- 成品打码规则 --> <h5>6.3 成品打码规则</h5> <p>参见受控的成品打码规则文件</p> <!-- 追溯要求 --> <h5>6.4 追溯要求</h5> <table class="spec-table"> <thead> <tr> <th>序号</th> <th>品类</th> <th>是否需要</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>电芯</td> <td><input type="checkbox" disabled checked/></td> <td></td> </tr> <tr> <td>2</td> <td>PCM测试数据</td> <td><input type="checkbox" disabled checked/></td> <td></td> </tr> <tr> <td>3</td> <td>老化测试数据</td> <td><input type="checkbox" disabled checked/></td> <td></td> </tr> <tr> <td>4</td> <td>成品测试数据</td> <td><input type="checkbox" disabled checked/></td> <td></td> </tr> <tr> <td>5</td> <td>气密性检测数据</td> <td><input type="checkbox" disabled/></td> <td></td> </tr> </tbody> </table> <!-- 附录上传 --> <hr class="divider"> <h4>附录</h4> <div> <label>附录一</label> <input type="file" @change="uploadNtcTable1" /> </div> <div> <label>附录二</label> <input type="file" @change="uploadNtcTable2" /> </div> </el-tabs> <div style="text-align: right; margin-top: 20px;"> <el-button @click="cancel">取消</el-button> <el-button type="primary" @click="submitForm">提交</el-button> </div> </el-dialog> </div> </template> <script> import { listData, getData, delData, addData, updateData } from "@/api/cms/productionSpecificationData"; import { parseTime } from "@/utils"; import { getToken } from "@/utils/auth"; export default { name: "productionSpecificationData", data() { return { // 加载状态 loading: true, // 单选/多选状态 single: true, multiple: true, // 显示搜索区域 showSearch: true, // 数据总条数 total: 0, // 数据列表 dataList: [], // 控制弹出层显示 open: false, // 弹出层标题 title: "", // 文件上传地址和头部 uploadUrl: process.env.VUE_APP_BASE_API + "/api/upload", uploadHeaders: { Authorization: "Bearer " + getToken() }, // 查询参数 queryParams: { pageNum: 1, pageSize: 10, customerName: "", projectName: "", batteryFinishedPartNumber: "", pcbSemiFinishedPartNumber: "", mainBoardSilkScreen: "", revision: "" }, // 表单数据 specForm: { id: null, customerName: "", projectName: "", batteryFinishedPartNumber: "", pcbSemiFinishedPartNumber: "", mainBoardSilkScreen: "", mainBoardPartNumber: "", revision: "", updateTime: "", changeDescription: "", updateBy: "", reviewedBy: "", approvedBy: "", confidentialLevel: "内部公开", materialColor: "", pcbPlacementDiagramTopImg: "", pcbPlacementDiagramBottomImg: "", topImgList: [], bottomImgList: [], // 功能测试字段 chargeOvercurrentProtection: "", chargeOvercurrentDelay: "", level1DischargeOvercurrentProtection: "", level1DischargeOvercurrentDelay: "", level2DischargeOvercurrentProtection: "", level2DischargeOvercurrentDelay: "", shortCircuitProtection: "", shortCircuitDelay: "", normalCharging: "", normalDischarging: "", openCircuitVoltage: "", packInternalResistanceMohm: "", jPpointContinuityTest: "", standardDischargeCapacity: "", fullCcCvCharging: "", fullStandby: "", fullConstantCurrentDischarge: "", fullChargeToShipmentVoltage: "", quickChargeToShipmentVoltage: "", quickStandby: "", cellPowerOnSequence: "", cellModel: "", cellBrand: "", maxCellInternalResistanceMohm: "", maxCellGroupResistanceDiffMohm: "", maxCellGroupVoltageDiffMv: "", maxCellGroupCapacityDiffMah: "", // 必检项 checkbox1: false, checkbox2: false, checkbox3: false, checkbox4: false, checkbox5: false, checkbox6: false, checkbox7: false, checkbox8: false, checkbox9: false, checkbox10: false, checkbox11: false, checkbox12: false, checkbox13: false, checkbox14: false, checkbox15: false }, // 版本记录列表 versionRecordList: [ { id: 1, serialNumber: 1, revision: "", updateTime: "", changeDescription: "", updateBy: "", reviewedBy: "", approvedBy: "" } ], // 表单验证规则 rules: { customerName: [ { required: true, message: "客户名称不能为空", trigger: "blur" } ], projectName: [ { required: true, message: "项目名称不能为空", trigger: "blur" } ], batteryFinishedPartNumber: [ { required: true, message: "成品料号不能为空", trigger: "blur" } ], pcbSemiFinishedPartNumber: [ { required: true, message: "半成品料号不能为空", trigger: "blur" } ], mainBoardSilkScreen: [ { required: true, message: "丝印不能为空", trigger: "blur" } ] }, // 多选选中数据 ids: [] }; }, created() { this.getList(); }, methods: { parseTime, // 获取数据列表 getList() { this.loading = true; listData(this.queryParams).then(response => { this.dataList = response.rows; this.total = response.total; this.loading = false; }); }, // 搜索查询 handleQuery() { this.queryParams.pageNum = 1; this.getList(); }, // 重置查询 resetQuery() { // 清空表单输入框内容(Element UI 提供的方法) if (this.$refs.queryForm) { this.$refs.queryForm.resetFields(); } // 强制重置 queryParams 数据 this.queryParams = { pageNum: 1, pageSize: 10, customerName: "", projectName: "", batteryFinishedPartNumber: "", pcbSemiFinishedPartNumber: "", mainBoardSilkScreen: "", materialColor: "" }; // 重新加载数据 this.handleQuery(); }, // 多选框变化 handleSelectionChange(selection) { this.ids = selection.map(item => item.id); this.single = selection.length !== 1; this.multiple = !selection.length; }, // 新增按钮点击 handleAdd() { this.resetForm(); this.open = true; this.title = "新增"; }, // 修改按钮点击 handleUpdate(row) { const id = row.id || this.ids[0]; getData(id).then(response => { this.specForm = response.data; this.open = true; this.title = "修改生产规格书"; }); }, // 删除按钮点击 handleDelete(row) { const ids = row.id ? [row.id] : this.ids; this.$confirm('是否确认删除编号为"' + ids.join(",") + '"的数据项?', "警告", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }).then(() => { delData(ids).then(() => { this.getList(); this.$message.success("删除成功"); }); }); }, // 导出按钮点击 handleExport() { this.download('cms/productionspecificationdata/export', { ...this.queryParams }, `data_${new Date().getTime()}.xlsx`); }, // 导出 Word 文档 handleExportPDF(row) { const id = row.id || this.ids[0]; getData(id).then(response => { this.specForm = response.data; // 这里可以添加导出 Word 的逻辑(如调用接口或前端库) this.$message.success("导出成功"); }); }, // 重置表单 resetForm() { this.specForm = { id: null, customerName: "", projectName: "", batteryFinishedPartNumber: "", pcbSemiFinishedPartNumber: "", mainBoardSilkScreen: "", mainBoardPartNumber: "", revision: "", updateTime: "", changeDescription: "", updateBy: "", reviewedBy: "", approvedBy: "", confidentialLevel: "内部公开", materialColor: "", pcbPlacementDiagramTopImg: "", pcbPlacementDiagramBottomImg: "", topImgList: [], bottomImgList: [], chargeOvercurrentProtection: "", chargeOvercurrentDelay: "", level1DischargeOvercurrentProtection: "", level1DischargeOvercurrentDelay: "", level2DischargeOvercurrentProtection: "", level2DischargeOvercurrentDelay: "", shortCircuitProtection: "", shortCircuitDelay: "", normalCharging: "", normalDischarging: "", openCircuitVoltage: "", packInternalResistanceMohm: "", jPpointContinuityTest: "", standardDischargeCapacity: "", fullCcCvCharging: "", fullStandby: "", fullConstantCurrentDischarge: "", fullChargeToShipmentVoltage: "", quickChargeToShipmentVoltage: "", quickStandby: "", cellPowerOnSequence: "", cellModel: "", cellBrand: "", maxCellInternalResistanceMohm: "", maxCellGroupResistanceDiffMohm: "", maxCellGroupVoltageDiffMv: "", maxCellGroupCapacityDiffMah: "", checkbox1: false, checkbox2: false, checkbox3: false, checkbox4: false, checkbox5: false, checkbox6: false, checkbox7: false, checkbox8: false, checkbox9: false, checkbox10: false, checkbox11: false, checkbox12: false, checkbox13: false, checkbox14: false, checkbox15: false }; this.versionRecordList = [ { revision: "", updateTime: "", changeDescription: "", updateBy: "", reviewedBy: "", approvedBy: "" } ]; }, addRow() { const newSerialNumber = this.versionRecordList.length + 1; this.versionRecordList.push({ id: this.nextId++, serialNumber: newSerialNumber, revision: '', updateTime: '', changeDescription: '', updateBy: '', reviewedBy: '', approvedBy: '' }); }, // 提交表单 submitForm() { this.$refs.form.validate(valid => { if (valid) { if (this.specForm.id) { updateData(this.specForm).then(() => { this.$message.success("修改成功"); this.open = false; this.getList(); }); } else { addData(this.specForm).then(() => { this.$message.success("新增成功"); this.open = false; this.getList(); }); } } }); }, // 取消按钮 cancel() { this.open = false; this.resetForm(); }, // 上传图片成功回调 handleTopImgSuccess(response) { this.specForm.pcbPlacementDiagramTopImg = response.url; }, handleBottomImgSuccess(response) { this.specForm.pcbPlacementDiagramBottomImg = response.url; }, // 上传图片前检查 beforeImgUpload(file) { const isValid = file.type.startsWith("image/"); if (!isValid) this.$message.error("只能上传图片文件"); return isValid; }, // 上传超出限制 handleExceed(files) { this.$message.warning(`最多上传 1 个文件,本次选择了 ${files.length} 个文件`); }, // 文件上传方法 uploadPcbTopImage(file) { console.log('上传PCB顶部图片', file); }, uploadPcbBottomImage(file) { console.log('上传PCB底部图片', file); }, uploadPcmPortImage(file) { console.log('上传PCM端口图', file); }, uploadBatteryPackOutputPortDiagramImg(file) { console.log('上传电池组输出端口图', file); }, uploadNtcTable1(file) { console.log('上传NTC表1', file); }, uploadNtcTable2(file) { console.log('上传NTC表2', file); } } }; </script> <style lang="scss" scoped> $app-main-bg: #f9f9f9; $app-border-color: #ebeef5; $app-primary-color: #409EFF; $app-success-color: #67c23a; $app-danger-color: #f56c6c; $app-warning-color: #e6a23c; $app-font-family: "Helvetica Neue", "PingFang SC", "Microsoft YaHei", sans-serif; .app-container { padding: 20px; font-family: $app-font-family; background-color: $app-main-bg; } // 表单区域优化 .el-form { background-color: #ffffff; padding: 20px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); margin-bottom: 20px; transition: all 0.3s ease-in-out; } .el-form-item { margin-bottom: 20px; } .el-input, .el-select { width: 100%; max-width: 250px; } .el-input__inner { border-radius: 6px; height: 36px; font-size: 14px; } // 按钮组样式 .mb8 { margin-bottom: 12px; } // 表格区域优化 .el-table { width: 100%; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); margin-bottom: 20px; } .el-table-column { text-align: center; } .el-table__header-wrapper th { background-color: $app-primary-color; color: #fff; font-weight: bold; border-bottom: none; } .el-table__row:hover { background-color: #f5f7fa; } .el-table__row { transition: background-color 0.3s ease; } // 分页样式 .pagination-container { margin-top: 20px; display: flex; justify-content: center; } // 弹出层样式 .el-dialog { border-radius: 10px; overflow: hidden; .el-dialog__header { background-color: $app-primary-color; color: #fff; padding: 15px 20px; font-size: 16px; font-weight: bold; } .el-dialog__body { padding: 20px; background-color: #fcfdff; border-top: 1px solid $app-border-color; border-bottom: 1px solid $app-border-color; } } .el-tabs { border: none; } // 表格样式(用于规格书内容) .spec-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; font-size: 14px; th, td { border: 1px solid $app-border-color; padding: 10px; text-align: center; } th { background-color: lighten($app-primary-color, 30%); color: #333; font-weight: bold; } td { background-color: #fff; } } // 分界线 .divider { border: 0; height: 1px; background: #eee; margin: 30px 0; } // 目录样式 .directory-container { max-width: 600px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; } .directory-container h2 { text-align: center; margin-bottom: 16px; color: $app-primary-color; } .spec-directory { list-style: none; padding: 0; margin: 0; line-height: 1.6; } .spec-directory li { text-align: left; margin: 6px 0; padding-left: 45%; position: relative; font-weight: 500; &::before { padding-left: 43%; content: "•"; position: absolute; left: 0; top: 0; color: $app-primary-color; font-size: 16px; } } // 按钮样式 .el-button--primary { background-color: $app-primary-color; border-color: $app-primary-color; color: #fff; } .el-button--success { background-color: $app-success-color; border-color: $app-success-color; color: #fff; } .el-button--danger { background-color: $app-danger-color; border-color: $app-danger-color; color: #fff; } .el-button--warning { background-color: $app-warning-color; border-color: $app-warning-color; color: #fff; } .el-button { border-radius: 6px; padding: 9px 15px; font-size: 13px; transition: all 0.3s ease-in-out; &:hover { opacity: 0.9; } i { margin-right: 5px; } } // 响应式布局 @media (max-width: 768px) { .el-input, .el-select { width: 100%; } .el-form-item__content { width: 100%; } .spec-directory li { padding-left: 10px; margin: 8px 0; &::before { display: none; } } } </style>
11-22
import cv2 import os import numpy as np from PIL import Image import json # 获取分类器路径 face_xml_location = 'haarcascade_frontalface_default.xml' detector = cv2.CascadeClassifier(face_xml_location) recognizer = cv2.face.LBPHFaceRecognizer_create() def get_images_and_labels(path): face_samples = [] ids = [] id_to_name = {} # 存储ID到姓名的映射 # 创建姓名到ID的映射 name_counter = 1 name_to_id = {} for file_name in os.listdir(path): if not file_name.lower().endswith(('.jpg', '.png')): continue # 正确解析文件名格式: User.[name].[number].jpg parts = file_name.split('.') if len(parts) < 3: continue name = parts[1] # 提取姓名部分 # 为每个新姓名分配唯一ID if name not in name_to_id: name_to_id[name] = name_counter id_to_name[name_counter] = name name_counter += 1 user_id = name_to_id[name] img_path = os.path.join(path, file_name) img = Image.open(img_path).convert('L') # 转换为灰度图 img_np = np.array(img, 'uint8') # 人脸检测 faces = detector.detectMultiScale(img_np) for (x, y, w, h) in faces: face_samples.append(img_np[y:y+h, x:x+w]) ids.append(user_id) return face_samples, ids, id_to_name print('训练中...') face_data_location = 'face_pic' faces, ids, id_to_name = get_images_and_labels(face_data_location) # 保存ID到姓名的映射 with open('id_to_name.json', 'w') as f: json.dump(id_to_name, f) # 转换为整数数组 ids = np.array(ids, dtype=np.int32) # 训练并保存模型 recognizer.train(faces, ids) train_data_location = os.path.join('train_data', 'train.yml') os.makedirs('train_data', exist_ok=True) recognizer.save(train_data_location) print(f'训练完成! 模型保存至: {train_data_location}') print(f'ID映射关系: {id_to_name}')import cv2 import numpy as np import os import json # 添加json模块用于加载映射关系 # 准备好识别方法 recognizer = cv2.face.LBPHFaceRecognizer_create() # 使用之前训练好的模型 train_data_location = os.path.join('.', 'train_data', 'train.yml') recognizer.read(train_data_location) # 加载ID到姓名的映射关系 id_to_name_path = os.path.join('.', 'id_to_name.json') with open(id_to_name_path, 'r') as f: id_to_name = json.load(f) # 将字典键转换为整数(JSON加载后键是字符串类型) id_to_name = {int(k): v for k, v in id_to_name.items()} # 再次调用人脸分类器 face_xml_location = os.path.join('.', 'haarcascade_frontalface_default.xml') face_cascade = cv2.CascadeClassifier(face_xml_location) # 加载一个字体,用于识别后标注名字 font = cv2.FONT_HERSHEY_SIMPLEX # 打开摄像头 camera = cv2.VideoCapture(0) while True: ret, im = camera.read() if not ret: continue gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) # 识别人脸 faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5) # 处理每个检测到的人脸 for (x, y, w, h) in faces: # 绘制人脸矩形框 cv2.rectangle(im, (x, y), (x+w, y+h), (0, 255, 0), 2) # 进行人脸识别 roi_gray = gray[y:y+h, x:x+w] img_id, confidence = recognizer.predict(roi_gray) # 根据置信度确定身份 if confidence < 40: # 置信度阈值,值越小匹配度越高 # 从映射关系中获取姓名 name = id_to_name.get(img_id, f"ID:{img_id}") else: name = "Unknown" # 显示姓名和置信度 confidence_text = f"{round(100 - confidence)}%" cv2.putText(im, name, (x, y-10), font, 1, (0, 255, 0), 2) cv2.putText(im, confidence_text, (x, y+h+25), font, 0.7, (0, 255, 0), 1) # 显示画面 cv2.imshow('Face Recognition', im) # 按q退出 if cv2.waitKey(10) & 0xFF == ord('q'): break # 释放资源 camera.release() cv2.destroyAllWindows()以上人脸识别模型训练和识别的代码提高置信度和光照不足情况的识别准确度
11-13
package com.kucun.data.entity; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import javax.persistence.Table; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonManagedReference; /** * 板材 * @author Administrator * */ @Entity @Table(name="bancai") public class Bancai { @Id private int id; @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "caizhi_id") // @JsonManagedReference // 标记为“主”关联方 private Caizhi caizhi; @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "mupi1_id") private Mupi mupi1; @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "mupi2_id") private Mupi mupi2; private Double houdu; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "kucun_id", referencedColumnName = "id") private Kucun kucun; public Kucun getKucun() { return kucun; } public void setKucun(Kucun kucun) { this.kucun = kucun; } public int getId() { return id; } public void setId(int id) { this.id = id; } public Caizhi getCaizhi() { return caizhi; } public void setCaizhi(Caizhi caizhi) { this.caizhi = caizhi; } public Mupi getMupi1() { return mupi1; } public void setMupi1(Mupi mupi1) { this.mupi1 = mupi1; } public Mupi getMupi2() { return mupi2; } public void setMupi2(Mupi mupi2) { this.mupi2 = mupi2; } public Double getHoudu() { return houdu; } public void setHoudu(Double houdu) { this.houdu = houdu; } public Bancai(int id, Caizhi caizhi, Mupi mupi1, Mupi mupi2, Double houdu) { super(); this.id = id; this.caizhi = caizhi; this.mupi1 = mupi1; this.mupi2 = mupi2; this.houdu = houdu; } public Bancai() { super(); } } package com.kucun.data.entity; import java.util.List; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * 板材材质 * @author Administrator * */ @Entity @Table(name="caizhi") @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) public class Caizhi { @Id private int id ; private String name; @OneToMany(mappedBy="caizhi") @JsonIgnore private List<Bancai> bancais; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "{id:" + id + ", name:" + name + "}"; } public Caizhi(int id, String name) { super(); this.id = id; this.name = name; } public Caizhi() { super(); } // 添加反向关联维护方法 public void addBancai(Bancai bancai) { bancais.add(bancai); bancai.setCaizhi(this); } // 添加移除方法 public void removeBancai(Bancai bancai) { bancais.remove(bancai); bancai.setCaizhi(null); } } package com.kucun.data.entity; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OneToOne; /** * 产品组件关联类 * @author Administrator * */ @Entity public class Chanpin_zujian { @Id private Integer id; @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Chanpin chanpins; @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Zujian zujian; @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Bancai bancai; private Double one_howmany; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Chanpin getChanpins() { return chanpins; } public void setChanpins(Chanpin chanpins) { this.chanpins = chanpins; } public Zujian getZujian() { return zujian; } public void setZujian(Zujian zujian) { this.zujian = zujian; } public Bancai getBancai() { return bancai; } public void setBancai(Bancai bancai) { this.bancai = bancai; } public Double getOne_howmany() { return one_howmany; } public void setOne_howmany(Double one_howmany) { this.one_howmany = one_howmany; } public Chanpin_zujian() { super(); // TODO Auto-generated constructor stub } } package com.kucun.data.entity; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.OneToMany; /** * 产品类 * @author Administrator * */ @Entity public class Chanpin { @Id private Integer id; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Dingdan_chanpin> dingdians; private String bianhao; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Chanpin_zujian> zujians; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public List<Dingdan_chanpin> getDingdians() { return dingdians; } public void setDingdians(List<Dingdan_chanpin> dingdians) { this.dingdians = dingdians; } public String getBianhao() { return bianhao; } public void setBianhao(String bianhao) { this.bianhao = bianhao; } public List<Chanpin_zujian> getZujians() { return zujians; } public void setZujians(List<Chanpin_zujian> zujians) { this.zujians = zujians; } public Chanpin(Integer id, List<Dingdan_chanpin> dingdians, String bianhao, List<Chanpin_zujian> zujians) { super(); this.id = id; this.dingdians = dingdians; this.bianhao = bianhao; this.zujians = zujians; } public Chanpin() { super(); // TODO Auto-generated constructor stub } } package com.kucun.data.entity; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.ManyToOne; /** * 一个订单中的产品组件订购板材数量 * @author Administrator * */ @Entity public class Dingdan_chanpin_zujian { @Id private Integer id; //订单 @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Dingdan dingdan; //板材 @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Bancai bancai; //产品组件 @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Chanpin_zujian zujians; //订购数 private Integer shuliang ; public Dingdan_chanpin_zujian() { super(); // TODO Auto-generated constructor stub } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Dingdan getDingdan() { return dingdan; } public void setDingdan(Dingdan dingdan) { this.dingdan = dingdan; } public Bancai getBancai() { return bancai; } public void setBancai(Bancai bancai) { this.bancai = bancai; } public Chanpin_zujian getZujians() { return zujians; } public void setZujians(Chanpin_zujian zujians) { this.zujians = zujians; } public Integer getShuliang() { return shuliang; } public void setShuliang(Integer shuliang) { this.shuliang = shuliang; } } package com.kucun.data.entity; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.ManyToOne; /** * 订单和产品关联 * @author Administrator * */ @Entity public class Dingdan_chanpin { @Id private Integer id; //产品信息 @ManyToOne (cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Dingdan dingdan; //产品数量 @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Chanpin chanpin; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Dingdan getDingdan() { return dingdan; } public void setDingdan(Dingdan dingdan) { this.dingdan = dingdan; } public Chanpin getChanping() { return chanpin; } public void setChanping(Chanpin chanping) { this.chanpin = chanping; } } package com.kucun.data.entity; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; /** * 订单 * @author Administrator * */ @Entity public class Dingdan { @Id private Integer id; //订单号 private String number; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Dingdan_chanpin> dingdan_chanpins; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Dingdan_chanpin_zujian> dingdan_chanpins_zujians; @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Chanpin> chanpins; public List<Dingdan_chanpin_zujian> getDingdan_chanpins_zujians() { return dingdan_chanpins_zujians; } public void setDingdan_chanpins_zujians(List<Dingdan_chanpin_zujian> dingdan_chanpins_zujians) { this.dingdan_chanpins_zujians = dingdan_chanpins_zujians; } public List<Chanpin> getChanpins() { return chanpins; } public void setChanpins(List<Chanpin> chanpins) { this.chanpins = chanpins; } public List<Dingdan_chanpin> getDingdan_chanpins() { return dingdan_chanpins; } public void setDingdan_chanpins(List<Dingdan_chanpin> dingdan_chanpins) { this.dingdan_chanpins = dingdan_chanpins; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public Dingdan(Integer id, String number) { super(); this.id = id; this.number = number; } public Dingdan() { super(); // TODO Auto-generated constructor stub } } package com.kucun.data.entity; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; /** * 通信类 * @author Administrator * */ public class Information { private static final ObjectMapper mapper = new ObjectMapper(); private Integer Status ; private String text; private Object data; public Integer getStatus() { return Status; } public void setStatus(Integer status) { Status = status; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public Information(Integer status, String text, Object data) { super(); Status = status; this.text = text; this.data = data; } @SuppressWarnings({"unchecked","rawtypes"}) public Information(Integer status, String text, String data, Class T) throws Exception { super(); Status = status; this.text = text; this.data = fromJson(data,T); } public Information() { super(); // TODO Auto-generated constructor stub } public String DataJson() throws JsonProcessingException { // Java对象转JSON return mapper.writeValueAsString(this); } @SuppressWarnings("unchecked") public <T> T fromJson(String json, Class<T> clazz) throws Exception { data= mapper.readValue(json, clazz); return (T) data; } public static Information NewSuccess(Object data) { return new Information(200, "success", data); } public static Information NewSuccess(String data) { return new Information(200, "success", data); } public static Information Newfail(Integer status,String text,Object data) { return new Information(status, "success", data); } } package com.kucun.data.entity; import java.util.Date; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Jinhuo { @Id private Integer id; private Dingdan dingdan; private Chanpin chanpin; private Zujian zujian; private Bancai bancai; private Integer shuliang; private Date date; private User user; public Jinhuo(Integer id, Dingdan dingdan, Chanpin chanpin, Zujian zujian, Bancai bancai, Integer shuliang, Date date, User user) { super(); this.id = id; this.dingdan = dingdan; this.chanpin = chanpin; this.zujian = zujian; this.bancai = bancai; this.shuliang = shuliang; this.date = date; this.user = user; } public Jinhuo() { super(); // TODO Auto-generated constructor stub } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Dingdan getDingdan() { return dingdan; } public void setDingdan(Dingdan dingdan) { this.dingdan = dingdan; } public Chanpin getChanpin() { return chanpin; } public void setChanpin(Chanpin chanpin) { this.chanpin = chanpin; } public Zujian getZujian() { return zujian; } public void setZujian(Zujian zujian) { this.zujian = zujian; } public Bancai getBancai() { return bancai; } public void setBancai(Bancai bancai) { this.bancai = bancai; } public Integer getShuliang() { return shuliang; } public void setShuliang(Integer shuliang) { this.shuliang = shuliang; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } } package com.kucun.data.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; /** * 库存 * @author Administrator * */ @Entity public class Kucun { @Id private Integer id; private Long shuliang; @OneToOne(fetch = FetchType.LAZY) // 正确映射 Bancai 实体 @JoinColumn(name = "bancai_id", referencedColumnName = "id") private Bancai bancai; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Bancai getBancai() { return bancai; } public void setBancai(Bancai bancai) { this.bancai = bancai; } public Long getShuliang() { return shuliang; } public void setShuliang(Long shuliang) { this.shuliang = shuliang; } public Kucun(Integer id, Bancai bancai, Long shuliang) { super(); this.id = id; this.bancai = bancai; this.shuliang = shuliang; } public Kucun() { super(); // TODO Auto-generated constructor stub } } package com.kucun.data.entity; import java.util.List; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.Table; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * 木皮 * @author Administrator * */ @Entity @Table(name="mupi") @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) public class Mupi { @Id private int id; private String name; /** * 是否有油漆 */ private Boolean you; // 添加 OneToMany 映射 @OneToMany(mappedBy = "mupi1") // 指向 Bancai 中的 mupi1 字段 @JsonIgnore private List<Bancai> bancaisForMupi1; @OneToMany(mappedBy = "mupi2") // 指向 Bancai 中的 mupi2 字段 @JsonIgnore private List<Bancai> bancaisForMupi2; public Mupi(int id, String name, List<Bancai> bancaisForMupi1, List<Bancai> bancaisForMupi2) { super(); this.id = id; this.name = name; this.bancaisForMupi1 = bancaisForMupi1; this.bancaisForMupi2 = bancaisForMupi2; } public List<Bancai> getBancaisForMupi1() { return bancaisForMupi1; } public void setBancaisForMupi1(List<Bancai> bancaisForMupi1) { this.bancaisForMupi1 = bancaisForMupi1; } public List<Bancai> getBancaisForMupi2() { return bancaisForMupi2; } public void setBancaisForMupi2(List<Bancai> bancaisForMupi2) { this.bancaisForMupi2 = bancaisForMupi2; } @Override public String toString() { return "{id:" + id + ", name:" + name + "}"; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Mupi(int id, String name) { super(); this.id = id; this.name = name; } public Mupi() { super(); } public Boolean getYou() { return you; } public void setYou(Boolean you) { this.you = you; } } package com.kucun.data.entity; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.ManyToOne; @Entity public class Zujian { @Id private Integer id; private String name; @ManyToOne private Chanpin_zujian chanpin; public Chanpin_zujian getChanpin() { return chanpin; } public void setChanpin(Chanpin_zujian chanpin) { this.chanpin = chanpin; } public Zujian() { super(); // TODO Auto-generated constructor stub } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }public interface DingdanRepository extends JpaRepository<Dingdan, Integer> { } <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>板材库存查询系统</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <style> .card-header { background: linear-gradient(to right, #1e3c72, #2a5298); color: white; } .search-section { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); border-radius: 10px; } .result-section { max-height: 70vh; overflow: auto; border-left: 3px solid #1e3c72; } .table-hover tbody tr:hover { background-color: rgba(42, 82, 152, 0.05); } .material-kucun { font-weight: 700; color: #1e3c72; } .material-kucun-low { color: #dc3545 !important; } .no-results { min-height: 200px; display: flex; align-items: center; justify-content: center; } .stats-card { border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } body { background-color: #f8f9fa; padding-bottom: 2rem; } .search-control { position: relative; } .search-icon { position: absolute; left: 12px; top: 12px; color: #6c757d; } .form-control.with-icon { padding-left: 35px; } .highlight { background-color: rgba(255, 255, 0, 0.3) !important; } .info-badge { font-size: 0.8rem; font-weight: normal; } </style> </head> <body> <div class="container py-4"> <!-- 标题部分 --> <div class="text-center mb-4"> <h1 class="text-primary"><i class="bi bi-boxes"></i> 板材库存管理系统</h1> <p class="text-muted">查询订单、产品、板材及库存信息</p> </div> <!-- 统计卡片 --> <div class="row mb-4"> <div class="col-md-3"> <div class="card stats-card border-primary"> <div class="card-body"> <h5 class="card-title">订单总数</h5> <p class="card-text fs-3 text-primary" id="orderCount">0</p> </div> </div> </div> <div class="col-md-3"> <div class="card stats-card border-info"> <div class="card-body"> <h5 class="card-title">产品种类</h5> <p class="card-text fs-3 text-info" id="productCount">0</p> </div> </div> </div> <div class="col-md-3"> <div class="card stats-card border-success"> <div class="card-body"> <h5 class="card-title">板材库存</h5> <p class="card-text fs-3 text-success" id="materialCount">0</p> </div> </div> </div> <div class="col-md-3"> <div class="card stats-card border-warning"> <div class="card-body"> <h5 class="card-title">库存总量</h5> <p class="card-text fs-3 text-warning" id="totalStock">0</p> </div> </div> </div> </div> <!-- 搜索区域 --> <div class="card search-section mb-4"> <div class="card-header"> <h5 class="mb-0"><i class="bi bi-search me-2"></i>高级搜索</h5> </div> <div class="card-body"> <div class="row g-3"> <!-- 订单搜索 --> <div class="col-md-6"> <div class="search-control"> <i class="bi bi-clipboard-search search-icon"></i> <input type="text" class="form-control with-icon" id="orderSearch" placeholder="搜索订单号..." aria-label="订单号搜索"> </div> </div> <!-- 产品搜索 --> <div class="col-md-6"> <div class="search-control"> <i class="bi bi-grid search-icon"></i> <input type="text" class="form-control with-icon" id="productSearch" placeholder="搜索产品编号..." aria-label="产品编号搜索"> </div> </div> <!-- 板材搜索 --> <div class="col-md-4"> <div class="search-control"> <i class="bi bi-box search-icon"></i> <input type="text" class="form-control with-icon" id="materialSearch" placeholder="搜索板材ID或材质..." aria-label="板材搜索"> </div> </div> <!-- 木皮搜索 --> <div class="col-md-4"> <div class="search-control"> <i class="bi bi-tree search-icon"></i> <input type="text" class="form-control with-icon" id="woodSearch" placeholder="搜索木皮名称..." aria-label="木皮搜索"> </div> </div> <!-- 厚度范围 --> <div class="col-md-4"> <div class="input-group"> <span class="input-group-text"><i class="bi bi-arrows-vertical"></i></span> <input type="number" class="form-control" id="minThickness" placeholder="最小厚度(mm)" min="0" step="0.1"> <span class="input-group-text">~</span> <input type="number" class="form-control" id="maxThickness" placeholder="最大厚度(mm)" min="0" step="0.1"> <button class="btn btn-primary" type="button" id="thicknessBtn"> <i class="bi bi-arrow-right"></i> </button> </div> </div> <!-- 库存阈值 --> <div class="col-md-12"> <div class="d-flex align-items-center"> <span class="me-2">库存阈值:</span> <div class="form-check form-check-inline"> <input class="form-check-input" type="checkbox" id="lowStockCheck" checked> <label class="form-check-label" for="lowStockCheck">显示低库存<small class="text-danger ms-1"><i class="bi bi-exclamation-triangle"></i></small></label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="checkbox" id="normalStockCheck" checked> <label class="form-check-label" for="normalStockCheck">显示正常库存</label> </div> <div class="ms-auto"> <button class="btn btn-primary me-2" id="searchBtn"> <i class="bi bi-search me-1"></i>搜索 </button> <button class="btn btn-outline-secondary" id="resetBtn"> <i class="bi bi-arrow-counterclockwise me-1"></i>重置 </button> </div> </div> </div> </div> </div> </div> <!-- 结果区域 --> <div class="card"> <div class="card-header d-flex justify-content-between align-items-center"> <h5 class="mb-0"><i class="bi bi-table me-2"></i>查询结果</h5> <div class="text-secondary"> <span id="resultCount">0</span> 条记录 <span class="ms-2"><i class="bi bi-info-circle"></i> <small>实时数据更新时间: <span id="lastUpdate">--:--:--</span></small></span> </div> </div> <div class="card-body result-section"> <div class="table-responsive"> <table class="table table-hover" id="resultTable"> <thead class="table-light sticky-top"> <tr> <th>订单号</th> <th>产品信息</th> <th>板材</th> <th>材质</th> <th>木皮1</th> <th>木皮2</th> <th>厚度(mm)</th> <th>库存</th> <th>操作</th> </tr> </thead> <tbody id="resultBody"> <!-- 数据加载中 --> <tr id="loadingRow"> <td colspan="9" class="text-center py-5"> <div class="d-flex align-items-center justify-content-center"> <div class="spinner-border text-primary" role="status"> <span class="visually-hidden">加载中...</span> </div> <div class="ms-3">正在加载数据,请稍候...</div> </div> </td> </tr> </tbody> </table> </div> <!-- 空结果提示 --> <div id="noResults" class="no-results text-center py-5" style="display: none;"> <div> <i class="bi bi-inboxes text-muted" style="font-size: 3rem;"></i> <h4 class="mt-3 text-muted">没有找到匹配的记录</h4> <p class="text-muted">请尝试调整您的搜索条件</p> </div> </div> </div> </div> </div> <script> $(document).ready(function() { // 模拟的后端返回数据(使用实体结构和属性名) const mockData = { Status: 200, text: "success", data: [ // 订单1 { id: 1001, number: "DD-20230515-001", dingdan_chanpins: [ { id: 1, chanpin: { id: 501, bianhao: "CP-1001", zujians: [ { id: 1, bancai: { id: 701, caizhi: { id: 1, name: "橡木" }, mupi1: { id: 101, name: "樱桃木", you: true }, mupi2: { id: 102, name: "胡桃木", you: false }, houdu: 15.5, kucun: { id: 801, shuliang: 3500 } } }, { id: 2, bancai: { id: 702, caizhi: { id: 2, name: "胡桃木" }, mupi1: { id: 103, name: "枫木", you: true }, mupi2: null, houdu: 18.0, kucun: { id: 802, shuliang: 1250 } } } ] } } ] }, // 订单2 { id: 1002, number: "DD-20230520-003", dingdan_chanpins: [ { id: 2, chanpin: { id: 502, bianhao: "CP-2005", zujians: [ { id: 3, bancai: { id: 703, caizhi: { id: 3, name: "桦木" }, mupi1: { id: 104, name: "白橡木", you: false }, mupi2: { id: 105, name: "黑檀木", you: true }, houdu: 12.0, kucun: { id: 803, shuliang: 4800 } } }, { id: 4, bancai: { id: 704, caizhi: { id: 4, name: "松木" }, mupi1: { id: 106, name: "红木", you: true }, mupi2: { id: 107, name: "柚木", you: true }, houdu: 22.5, kucun: { id: 804, shuliang: 300 } } } ] } } ] } ] }; // 通信函数 async function https(url, data, callback) { console.log(`Sending request to ${url} with data:`, data); // 模拟API响应 await new Promise(resolve => setTimeout(resolve, 800)); // 使用模拟数据 if (callback) callback(mockData); return mockData; } // 主搜索函数 function performSearch() { // 显示加载状态 $("#loadingRow").show(); $("#noResults").hide(); // 收集搜索条件 const searchParams = { orderNumber: $("#orderSearch").val().trim(), productCode: $("#productSearch").val().trim(), material: $("#materialSearch").val().trim(), woodSkin: $("#woodSearch").val().trim(), minThickness: $("#minThickness").val(), maxThickness: $("#maxThickness").val(), lowStock: $("#lowStockCheck").prop("checked"), normalStock: $("#normalStockCheck").prop("checked") }; // 调用API https('/api/search', searchParams, function(response) { // 隐藏加载状态 $("#loadingRow").hide(); if (response.Status === 200) { // 处理数据 processSearchResults(response.data); // 更新时间 const now = new Date(); $("#lastUpdate").text( `${now.getHours().toString().padStart(2, '0')}:` + `${now.getMinutes().toString().padStart(2, '0')}:` + `${now.getSeconds().toString().padStart(2, '0')}` ); } else { showError("数据加载失败: " + response.text); } }); } // 处理搜索结果 function processSearchResults(data) { // 清空表格 $("#resultBody").empty(); // 统计变量 let itemCount = 0; const orderIds = new Set(); const productIds = new Set(); const materialIds = new Set(); let totalStock = 0; // 遍历订单->产品->组件层级 for (const order of data) { orderIds.add(order.id); for (const orderProduct of order.dingdan_chanpins) { const product = orderProduct.chanpin; productIds.add(product.id); for (const component of product.zujians) { const material = component.bancai; materialIds.add(material.id); // 库存统计 const stock = material.kucun.shuliang; totalStock += stock; // 匹配搜索条件 if (matchesSearchCriteria(order, product, material, stock)) { itemCount++; addTableRow(order, product, component); } } } } // 更新统计数据 $("#orderCount").text(orderIds.size); $("#productCount").text(productIds.size); $("#materialCount").text(materialIds.size); $("#totalStock").text(totalStock.toLocaleString()); $("#resultCount").text(itemCount); // 显示空结果信息 if (itemCount === 0) { $("#noResults").show(); } } // 检查是否符合搜索条件 function matchesSearchCriteria(order, product, material, stock) { const searchParams = { orderNumber: $("#orderSearch").val().trim(), productCode: $("#productSearch").val().trim(), material: $("#materialSearch").val().trim(), woodSkin: $("#woodSearch").val().trim(), minThickness: $("#minThickness").val(), maxThickness: $("#maxThickness").val(), lowStock: $("#lowStockCheck").prop("checked"), normalStock: $("#normalStockCheck").prop("checked") }; // 订单号匹配 if (searchParams.orderNumber && !order.number.toLowerCase().includes(searchParams.orderNumber.toLowerCase())) { return false; } // 产品号匹配 if (searchParams.productCode && !product.bianhao.toLowerCase().includes(searchParams.productCode.toLowerCase())) { return false; } // 材质匹配 if (searchParams.material && !material.caizhi.name.toLowerCase().includes(searchParams.material.toLowerCase()) && material.id.toString() !== searchParams.material) { return false; } // 木皮匹配 if (searchParams.woodSkin) { const wood = searchParams.woodSkin.toLowerCase 没写完
06-06
import tkinter as tk from tkinter import filedialog, messagebox, ttk import numpy as np import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk from matplotlib.widgets import Slider, TextBox import struct import os import re import logging # 配置日志系统 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # 设置matplotlib中文字体支持 plt.rcParams["font.family"] = ["SimHei"] plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 class BinaryVisualizer: def __init__(self): self.root = None self.menubar = None # 显式引用菜单栏 self.files = {} # 存储已加载的文件数据 self.current_file_alias = None self.current_frame = 0 self.width = 512 # 默认宽度 self.height = 341 # 默认高度 self.block_size = self.width * self.height # 每帧的像素数 self.value_offset = 0 # 数据偏移值 self.display_mode = "固定范围" # 默认显示模式 self.custom_min = -1000 # 默认自定义最小值 self.custom_max = 1000 # 默认自定义最大值 self.operations = {} # 存储运算结果 self.current_display_source = "original" # 当前显示源: "original" 或 "operation" self.selected_operation = None # 当前选择的运算结果 # 允许的公式字符集合 self.allowed_formula_chars = set(['+', '-', '*', '/', '(', ')', 'numpy.', 'np.', '.', '_', ' ']) self.allowed_formula_chars.update([str(i) for i in range(10)]) # 初始化matplotlib设置 plt.rcParams["figure.figsize"] = (10, 7) plt.rcParams["image.cmap"] = "gray" def detect_possible_resolutions(self, pixel_count): """检测可能的分辨率""" possible_resolutions = [] max_width = int(np.sqrt(pixel_count)) * 2 for width in range(100, max_width, 2): # 步长为2,确保宽度是偶数 if pixel_count % width == 0: height = pixel_count // width possible_resolutions.append((width, height)) return possible_resolutions def load_data(self, file_path=None, alias=None): """加载二进制数据文件""" if not file_path: file_path = filedialog.askopenfilename( title="选择二进制数据文件", filetypes=[("二进制文件", "*.dat"), ("所有文件", "*.*")] ) if not file_path: return False # 自动生成别称(A, B, C, ...) if not alias: used_aliases = set(self.files.keys()) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': if c not in used_aliases: alias = c break else: messagebox.showerror("错误", "无法生成文件别称,已达到最大限制") return False try: with open(file_path, 'rb') as bin_file: data = bin_file.read() total_bytes = len(data) # 每像素2字节,计算可能的帧数 pixel_count = total_bytes // 2 possible_frames = pixel_count // self.block_size if possible_frames == 0: # 尝试检测可能的分辨率 possible_resolutions = self.detect_possible_resolutions(pixel_count) if not possible_resolutions: messagebox.showerror("错误", f"文件太小,无法包含一个完整的{self.width}x{self.height}帧") return False # 让用户选择分辨率 resolution_window = tk.Toplevel(self.root) resolution_window.title("选择分辨率") tk.Label(resolution_window, text="无法使用默认分辨率,请选择或输入:").pack(padx=10, pady=10) resolution_frame = tk.Frame(resolution_window) resolution_frame.pack(padx=10, pady=5) tk.Label(resolution_frame, text="宽度:").pack(side=tk.LEFT, padx=5) width_entry = tk.Entry(resolution_frame, width=10) width_entry.insert(0, str(self.width)) width_entry.pack(side=tk.LEFT, padx=5) tk.Label(resolution_frame, text="高度:").pack(side=tk.LEFT, padx=5) height_entry = tk.Entry(resolution_frame, width=10) height_entry.insert(0, str(self.height)) height_entry.pack(side=tk.LEFT, padx=5) def apply_resolution(): try: self.width = int(width_entry.get()) self.height = int(height_entry.get()) self.block_size = self.width * self.height resolution_window.destroy() self.load_data(file_path, alias) # 重新加载 except ValueError: messagebox.showerror("错误", "请输入有效的整数") tk.Button(resolution_window, text="应用", command=apply_resolution).pack(pady=10) return False logger.info(f"文件大小: {total_bytes} 字节") logger.info(f"可能的帧数: {possible_frames}") # 解析每一帧数据 frames = [] for frame_idx in range(possible_frames): start = frame_idx * self.block_size * 2 # 每像素2字节 end = start + self.block_size * 2 frame_data = data[start:end] # 确保帧大小是偶数 if len(frame_data) % 2 != 0: logger.warning(f"帧 {frame_idx+1} 字节数为奇数,最后一个字节将被忽略") frame_data = frame_data[:-1] # 解析为有符号整数并应用偏移 frame_values = [] for i in range(0, len(frame_data), 2): low_byte = frame_data[i] high_byte = frame_data[i+1] try: signed_value = struct.unpack('<h', bytes([low_byte, high_byte]))[0] except struct.error as e: logger.error(f"解析帧 {frame_idx+1} 时出错: {e}") signed_value = 0 # 默认值 adjusted_value = signed_value + self.value_offset frame_values.append((signed_value, adjusted_value)) # 转换为二维数组 try: signed_array = np.array([x[0] for x in frame_values]).reshape((self.height, self.width)) adjusted_array = np.array([x[1] for x in frame_values]).reshape((self.height, self.width)) frames.append((signed_array, adjusted_array)) except ValueError as e: logger.error(f"重塑帧 {frame_idx+1} 时出错: {e}") # 添加空白帧作为替代 frames.append((np.zeros((self.height, self.width)), np.zeros((self.height, self.width)))) # 存储文件数据 self.files[alias] = { 'path': file_path, 'data': data, 'frames': frames } # 如果是第一个文件或明确指定了当前文件 if not self.current_file_alias or alias == self.current_file_alias: self.current_file_alias = alias self.current_frame = 0 # 更新UI if self.slider_frame: self.slider_frame.valmax = len(frames) - 1 self.slider_frame.set_val(0) self.slider_frame.set_active(True) # 启用滑块 if self.textbox_frame: self.textbox_frame.set_val("1") self.update_display_range() # 更新文件选择菜单 self.update_file_menu() logger.info(f"成功加载文件 {alias}: {os.path.basename(file_path)}") return True except Exception as e: messagebox.showerror("错误", f"加载文件时出错: {str(e)}") logger.error(f"加载文件时出错: {e}", exc_info=True) return False def load_multiple_files(self): """加载多个二进制数据文件""" file_paths = filedialog.askopenfilenames( title="选择多个二进制数据文件", filetypes=[("二进制文件", "*.dat"), ("所有文件", "*.*")] ) if not file_paths: return for file_path in file_paths: self.load_data(file_path) def select_file(self, alias): """选择当前文件""" if alias in self.files: self.current_file_alias = alias self.current_frame = 0 # 更新UI if self.slider_frame: self.slider_frame.valmax = len(self.files[alias]['frames']) - 1 self.slider_frame.set_val(0) if self.textbox_frame: self.textbox_frame.set_val("1") self.update_display_range() def on_frame_change(self, val): """帧滑块值改变时调用""" frame_idx = int(val) if self.current_file_alias and self.current_file_alias in self.files: max_frame = len(self.files[self.current_file_alias]['frames']) - 1 if frame_idx > max_frame: frame_idx = max_frame self.slider_frame.set_val(frame_idx) self.current_frame = frame_idx self.textbox_frame.set_val(str(frame_idx + 1)) self.update_display_range() def on_frame_number_submit(self, text): """帧号输入提交时调用""" try: frame_num = int(text) if self.current_file_alias and self.current_file_alias in self.files: max_frame = len(self.files[self.current_file_alias]['frames']) if frame_num < 1 or frame_num > max_frame: messagebox.showinfo("提示", f"请输入1到{max_frame}之间的帧号") frame_num = min(max(frame_num, 1), max_frame) self.current_frame = frame_num - 1 self.slider_frame.set_val(self.current_frame) self.update_display_range() except ValueError: messagebox.showerror("错误", "请输入有效的整数") def set_display_mode(self, mode): """设置显示模式""" self.display_mode = mode self.update_display_range() def set_custom_range(self): """设置自定义显示范围""" range_window = tk.Toplevel(self.root) range_window.title("设置自定义范围") tk.Label(range_window, text="最小值:").pack(padx=10, pady=5) min_entry = tk.Entry(range_window, width=10) min_entry.insert(0, str(self.custom_min)) min_entry.pack(padx=10) tk.Label(range_window, text="最大值:").pack(padx=10, pady=5) max_entry = tk.Entry(range_window, width=10) max_entry.insert(0, str(self.custom_max)) max_entry.pack(padx=10) def apply_range(): try: self.custom_min = int(min_entry.get()) self.custom_max = int(max_entry.get()) if self.custom_min >= self.custom_max: messagebox.showerror("错误", "最小值必须小于最大值") return self.display_mode = "自定义" self.update_display_range() range_window.destroy() except ValueError: messagebox.showerror("错误", "请输入有效的整数") tk.Button(range_window, text="应用", command=apply_range).pack(pady=10) def update_display_range(self): """更新显示范围""" if not self.current_file_alias or self.current_file_alias not in self.files: return frames = self.files[self.current_file_alias]['frames'] if not frames or self.current_frame >= len(frames): return current_frame_data = frames[self.current_frame][1] # 使用偏移后的数据 try: if self.display_mode == "固定范围": vmin = self.custom_min vmax = self.custom_max elif self.display_mode == "帧平均±1000": frame_mean = np.mean(current_frame_data) vmin = max(frame_mean - 1000, np.min(current_frame_data)) vmax = min(frame_mean + 1000, np.max(current_frame_data)) elif self.display_mode == "帧平均±200": frame_mean = np.mean(current_frame_data) vmin = max(frame_mean - 200, np.min(current_frame_data)) vmax = min(frame_mean + 200, np.max(current_frame_data)) else: # 自定义 vmin = self.custom_min vmax = self.custom_max # 更新图像显示 self.current_image.set_data(current_frame_data) self.current_image.set_clim(vmin, vmax) # 更新直方图 self.ax_hist.clear() self.ax_hist.hist(current_frame_data.flatten(), bins=50, range=(vmin, vmax), alpha=0.7) self.ax_hist.set_title("像素值分布直方图") self.ax_hist.set_xlabel("像素值") self.ax_hist.set_ylabel("频次") self.ax_hist.grid(True) # 更新标题 frame_mean = np.mean(current_frame_data) frame_std = np.std(current_frame_data) # 安全处理文件名 try: if self.current_file_alias in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': file_name = f"文件 {self.current_file_alias}" else: file_name = self.current_file_alias except Exception as e: logger.error(f"处理文件名时出错: {e}") file_name = "未知文件" self.fig.suptitle(f"{file_name} - 帧 {self.current_frame+1}/{len(frames)} - 均值: {frame_mean:.2f}, 标准差: {frame_std:.2f}") self.canvas.draw() except Exception as e: logger.error(f"更新显示范围时出错: {e}") messagebox.showerror("显示错误", f"更新显示时出错: {str(e)}") def export_to_txt(self): """导出当前帧为TXT文件""" if not self.current_file_alias or self.current_file_alias not in self.files: messagebox.showinfo("提示", "请先加载文件") return frames = self.files[self.current_file_alias]['frames'] if not frames or self.current_frame >= len(frames): messagebox.showinfo("提示", "无效的帧") return current_frame_data = frames[self.current_frame][1] # 使用偏移后的数据 file_path = filedialog.asksaveasfilename( title="保存当前帧为文本文件", defaultextension=".txt", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] ) if not file_path: return try: with open(file_path, 'w') as txt_file: # 写入头部信息 txt_file.write(f"# 数据导出 - 文件: {self.current_file_alias}\n") txt_file.write(f"# 帧号: {self.current_frame+1}/{len(frames)}\n") txt_file.write(f"# 分辨率: {self.width}x{self.height}\n") txt_file.write(f"# 像素值范围: {np.min(current_frame_data)} - {np.max(current_frame_data)}\n") txt_file.write(f"# 均值: {np.mean(current_frame_data):.2f}, 标准差: {np.std(current_frame_data):.2f}\n") txt_file.write("#\n") # 写入数据 for row in current_frame_data: row_str = ' '.join([f"{float(val):.6f}" for val in row]) txt_file.write(row_str + '\n') messagebox.showinfo("成功", f"帧数据已保存到 {file_path}") except Exception as e: messagebox.showerror("错误", f"保存文件时出错: {str(e)}") def set_resolution(self): """设置图像分辨率""" resolution_window = tk.Toplevel(self.root) resolution_window.title("设置分辨率") tk.Label(resolution_window, text="宽度:").pack(padx=10, pady=5) width_entry = tk.Entry(resolution_window, width=10) width_entry.insert(0, str(self.width)) width_entry.pack(padx=10) tk.Label(resolution_window, text="高度:").pack(padx=10, pady=5) height_entry = tk.Entry(resolution_window, width=10) height_entry.insert(0, str(self.height)) height_entry.pack(padx=10) def apply_resolution(): try: new_width = int(width_entry.get()) new_height = int(height_entry.get()) if new_width <= 0 or new_height <= 0: messagebox.showerror("错误", "宽度和高度必须为正整数") return self.width = new_width self.height = new_height self.block_size = self.width * self.height # 重新加载当前文件以应用新的分辨率 if self.current_file_alias and self.current_file_alias in self.files: file_path = self.files[self.current_file_alias]['path'] self.load_data(file_path, self.current_file_alias) resolution_window.destroy() except ValueError: messagebox.showerror("错误", "请输入有效的整数") tk.Button(resolution_window, text="应用", command=apply_resolution).pack(pady=10) def set_offset(self): """设置数据偏移值""" offset_window = tk.Toplevel(self.root) offset_window.title("设置偏移值") tk.Label(offset_window, text="偏移值:").pack(padx=10, pady=5) offset_entry = tk.Entry(offset_window, width=10) offset_entry.insert(0, str(self.value_offset)) offset_entry.pack(padx=10) def apply_offset(): try: self.value_offset = int(offset_entry.get()) # 重新加载当前文件以应用新的偏移值 if self.current_file_alias and self.current_file_alias in self.files: file_path = self.files[self.current_file_alias]['path'] self.load_data(file_path, self.current_file_alias) offset_window.destroy() except ValueError: messagebox.showerror("错误", "请输入有效的整数") tk.Button(offset_window, text="应用", command=apply_offset).pack(pady=10) def create_formula(self): """创建公式窗口""" if len(self.files) < 2: messagebox.showinfo("提示", "请先加载至少两个文件") return formula_window = tk.Toplevel(self.root) formula_window.title("公式计算") formula_window.geometry("600x400") # 可用文件列表 tk.Label(formula_window, text="可用文件:").pack(padx=10, pady=5, anchor="w") files_frame = tk.Frame(formula_window) files_frame.pack(padx=10, fill="x") file_listbox = tk.Listbox(files_frame, width=50) file_listbox.pack(side=tk.LEFT, fill="both", expand=True) scrollbar = tk.Scrollbar(files_frame, orient="vertical", command=file_listbox.yview) scrollbar.pack(side=tk.RIGHT, fill="y") file_listbox.config(yscrollcommand=scrollbar.set) for alias in sorted(self.files.keys()): file_listbox.insert(tk.END, f"文件 {alias}: {os.path.basename(self.files[alias]['path'])}") # 公式输入 tk.Label(formula_window, text="公式输入 (使用文件别称如 A,B,C 进行 +-*/ 运算):").pack(padx=10, pady=5, anchor="w") formula_frame = tk.Frame(formula_window) formula_frame.pack(padx=10, fill="x") formula_entry = tk.Entry(formula_frame, width=50) formula_entry.pack(side=tk.LEFT, fill="x", expand=True) # 插入文件按钮 def insert_file_alias(): selection = file_listbox.curselection() if selection: alias = sorted(self.files.keys())[selection[0]] formula_entry.insert(tk.INSERT, alias) tk.Button(formula_frame, text="插入文件", command=insert_file_alias).pack(side=tk.LEFT, padx=5) # 帧选择 tk.Label(formula_window, text="选择帧:").pack(padx=10, pady=5, anchor="w") frame_frame = tk.Frame(formula_window) frame_frame.pack(padx=10, fill="x") frame_var = tk.StringVar(value="same") frame_options = [ ("所有文件使用相同帧", "same"), ("为每个文件指定帧", "different") ] for text, value in frame_options: tk.Radiobutton(frame_frame, text=text, variable=frame_var, value=value).pack(anchor="w") # 相同帧选择 same_frame_frame = tk.Frame(formula_window) same_frame_frame.pack(padx=10, pady=5, fill="x") tk.Label(same_frame_frame, text="帧号:").pack(side=tk.LEFT) same_frame_entry = tk.Entry(same_frame_frame, width=10) same_frame_entry.insert(0, "1") same_frame_entry.pack(side=tk.LEFT, padx=5) # 结果名称 tk.Label(formula_window, text="结果名称:").pack(padx=10, pady=5, anchor="w") result_name_frame = tk.Frame(formula_window) result_name_frame.pack(padx=10, fill="x") result_name_entry = tk.Entry(result_name_frame, width=20) result_name_entry.insert(0, "Result") result_name_entry.pack(side=tk.LEFT, padx=5) # 计算按钮 def calculate_formula(): formula = formula_entry.get().strip() if not formula: messagebox.showerror("错误", "请输入公式") return # 验证公式 valid_aliases = set(self.files.keys()) used_aliases = set(re.findall(r'[A-Z]', formula)) if not used_aliases.issubset(valid_aliases): invalid_aliases = used_aliases - valid_aliases messagebox.showerror("错误", f"公式中包含无效的文件别称: {', '.join(invalid_aliases)}") return # 额外验证公式安全性 if not self._validate_formula(formula): return try: frame_mode = frame_var.get() if frame_mode == "same": try: frame_num = int(same_frame_entry.get()) except ValueError: messagebox.showerror("错误", "请输入有效的帧号") return # 检查所有文件是否有该帧 for alias in used_aliases: if frame_num < 1 or frame_num > len(self.files[alias]['frames']): messagebox.showerror("错误", f"文件 {alias} 没有帧 {frame_num}") return # 创建变量字典 variables = {} for alias in used_aliases: variables[alias] = self.files[alias]['frames'][frame_num-1][1] # 使用偏移后的数据 # 计算结果 try: result = self.safe_eval(formula, variables) if result is None: raise ValueError("公式计算失败") except Exception as e: messagebox.showerror("错误", f"公式计算出错: {str(e)}") return # 保存结果 result_name = result_name_entry.get().strip() if not result_name: result_name = "Result" # 检查结果名称是否已存在 if result_name in self.operations: if not messagebox.askyesno("确认", f"结果名称 '{result_name}' 已存在,是否覆盖?"): return # 保存结果 self.operations[result_name] = { 'formula': formula, 'frame_mode': frame_mode, 'frame_num': frame_num if frame_mode == "same" else None, 'result': result } messagebox.showinfo("成功", f"公式计算成功,结果已保存为 '{result_name}'") formula_window.destroy() # 更新操作菜单 self.update_operations_menu() except Exception as e: messagebox.showerror("错误", f"执行计算时出错: {str(e)}") logger.error(f"执行计算时出错: {e}", exc_info=True) tk.Button(formula_window, text="计算", command=calculate_formula).pack(pady=10) def safe_eval(self, formula, variables): """安全执行公式计算""" # 验证公式 for char in formula: if char not in self.allowed_formula_chars: logger.warning(f"公式包含不允许的字符: {char}") return None # 禁用所有内置函数,只允许numpy return eval(formula, {'__builtins__': None, 'np': np, 'numpy': np}, variables) def _validate_formula(self, formula): """验证公式安全性""" for char in formula: if char not in self.allowed_formula_chars: messagebox.showerror("错误", f"公式包含不允许的字符: {char}") logger.error(f"非法公式: {formula}") return False return True def show_operation_result(self, name): """显示运算结果""" if name in self.operations: self.selected_operation = name result = self.operations[name]['result'] # 更新图像显示 self.current_image.set_data(result) # 更新显示范围 vmin = np.min(result) vmax = np.max(result) self.current_image.set_clim(vmin, vmax) # 更新直方图 self.ax_hist.clear() self.ax_hist.hist(result.flatten(), bins=50, range=(vmin, vmax), alpha=0.7) self.ax_hist.set_title("运算结果像素值分布") self.ax_hist.set_xlabel("像素值") self.ax_hist.set_ylabel("频次") self.ax_hist.grid(True) # 更新标题 formula = self.operations[name]['formula'] self.fig.suptitle(f"运算结果 - {name}: {formula}") self.canvas.draw() self.current_display_source = "operation" self.source_var.set("运算结果") # 更新数据源菜单 self._update_source_menu() def save_operation_result(self): """保存当前运算结果为TXT文件""" if not self.operations: messagebox.showinfo("提示", "没有可用的运算结果") return # 获取当前显示的运算结果名称 current_name = None if self.current_display_source == "operation" and self.selected_operation: current_name = self.selected_operation # 如果没有当前显示的结果,让用户选择 if not current_name or current_name not in self.operations: if len(self.operations) == 1: current_name = list(self.operations.keys())[0] else: # 创建选择窗口 select_window = tk.Toplevel(self.root) select_window.title("选择运算结果") tk.Label(select_window, text="请选择要保存的运算结果:").pack(padx=10, pady=10) result_listbox = tk.Listbox(select_window, width=50) result_listbox.pack(padx=10, fill="both", expand=True) for name in sorted(self.operations.keys()): result_listbox.insert(tk.END, f"{name}: {self.operations[name]['formula']}") def on_select(): selection = result_listbox.curselection() if selection: selected_name = sorted(self.operations.keys())[selection[0]] select_window.destroy() self._save_result_to_txt(selected_name) button_frame = tk.Frame(select_window) button_frame.pack(pady=10) tk.Button(button_frame, text="确定", command=on_select).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="取消", command=select_window.destroy).pack(side=tk.LEFT, padx=10) return else: self._save_result_to_txt(current_name) def _save_result_to_txt(self, name): """实际保存运算结果到TXT文件,支持多种格式""" result = self.operations[name]['result'] file_path = filedialog.asksaveasfilename( title="保存运算结果为文本文件", defaultextension=".txt", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] ) if not file_path: return # 选择保存格式 format_window = tk.Toplevel(self.root) format_window.title("选择保存格式") format_var = tk.StringVar(value="decimal") format_frame = tk.Frame(format_window) format_frame.pack(padx=10, pady=10) format_options = [ ("带符号十进制数", "signed"), ("十进制数", "decimal"), ("十六进制", "hex") ] for text, value in format_options: tk.Radiobutton(format_frame, text=text, variable=format_var, value=value).pack(anchor="w", pady=2) def save_with_format(): try: format_type = format_var.get() format_window.destroy() with open(file_path, 'w') as txt_file: # 写入公式信息 txt_file.write(f"# 运算公式: {self.operations[name]['formula']}\n") txt_file.write(f"# 保存格式: {dict(format_options).get(format_type, format_type)}\n") txt_file.write("#\n") # 写入数据 for row in result: if format_type == "hex": # 转换为十六进制格式 row_str = ' '.join([f"{int(val):04X}" for val in row]) elif format_type == "signed": # 带符号十进制 row_str = ' '.join([f"{int(val):d}" for val in row]) else: # decimal # 十进制 row_str = ' '.join([f"{int(val):d}" for val in row]) txt_file.write(row_str + '\n') messagebox.showinfo("成功", f"运算结果已保存到 {file_path}") except Exception as e: messagebox.showerror("错误", f"保存文件时出错: {str(e)}") tk.Button(format_window, text="保存", command=save_with_format).pack(pady=10) tk.Button(format_window, text="取消", command=format_window.destroy).pack(pady=10) def export_to_txt(self): """导出当前帧为TXT文件,支持多种格式""" if not self.current_file_alias or self.current_file_alias not in self.files: messagebox.showinfo("提示", "请先加载文件") return frames = self.files[self.current_file_alias]['frames'] if not frames or self.current_frame >= len(frames): messagebox.showinfo("提示", "无效的帧") return current_frame_data = frames[self.current_frame][1] # 使用偏移后的数据 file_path = filedialog.asksaveasfilename( title="保存当前帧为文本文件", defaultextension=".txt", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] ) if not file_path: return # 选择保存格式 format_window = tk.Toplevel(self.root) format_window.title("选择保存格式") format_var = tk.StringVar(value="decimal") format_frame = tk.Frame(format_window) format_frame.pack(padx=10, pady=10) format_options = [ ("带符号十进制数", "signed"), ("十进制数", "decimal"), ("十六进制", "hex") ] for text, value in format_options: tk.Radiobutton(format_frame, text=text, variable=format_var, value=value).pack(anchor="w", pady=2) def save_with_format(): try: format_type = format_var.get() format_window.destroy() with open(file_path, 'w') as txt_file: # 写入头部信息 txt_file.write(f"# 数据导出 - 文件: {self.current_file_alias}\n") txt_file.write(f"# 帧号: {self.current_frame+1}/{len(frames)}\n") txt_file.write(f"# 分辨率: {self.width}x{self.height}\n") txt_file.write(f"# 像素值范围: {np.min(current_frame_data)} - {np.max(current_frame_data)}\n") txt_file.write(f"# 均值: {np.mean(current_frame_data):.2f}, 标准差: {np.std(current_frame_data):.2f}\n") txt_file.write("#\n") # 写入数据 for row in current_frame_data: if format_type == "hex": # 转换为十六进制格式 row_str = ' '.join([f"{int(val):04X}" for val in row]) elif format_type == "signed": # 带符号十进制 row_str = ' '.join([f"{int(val):d}" for val in row]) else: # decimal # 十进制 row_str = ' '.join([f"{int(val):d}" for val in row]) txt_file.write(row_str + '\n') messagebox.showinfo("成功", f"帧数据已保存到 {file_path}") except Exception as e: messagebox.showerror("错误", f"保存文件时出错: {str(e)}") tk.Button(format_window, text="保存", command=save_with_format).pack(pady=10) tk.Button(format_window, text="取消", command=format_window.destroy).pack(pady=10) def set_display_source(self, source): """设置显示源""" self.current_display_source = source if source == "original": # 显示原始数据 if self.current_file_alias and self.current_file_alias in self.files: frames = self.files[self.current_file_alias]['frames'] if frames and self.current_frame < len(frames): current_frame_data = frames[self.current_frame][1] # 使用偏移后的数据 # 更新图像显示 self.current_image.set_data(current_frame_data) # 更新显示范围 self.update_display_range() elif source == "operation" and self.selected_operation and self.selected_operation in self.operations: # 显示运算结果 self.show_operation_result(self.selected_operation) def on_source_changed(self, event=None): """数据源选择变化时调用""" source = self.source_var.get() if source == "原始数据": self.set_display_source("original") elif source == "运算结果" and self.operations: if not self.selected_operation or self.selected_operation not in self.operations: # 如果没有选择运算结果,选择第一个 self.selected_operation = list(self.operations.keys())[0] self.set_display_source("operation") def _get_or_create_menu(self, menu_name): """获取或创建菜单""" try: if not self.menubar: self.menubar = tk.Menu(self.root) self.root.config(menu=self.menubar) # 检查菜单是否已存在 for i in range(self.menubar.index('end') + 1): menu_label = self.menubar.entrycget(i, 'label') if menu_label == menu_name: return self.menubar.nametowidget(self.menubar.entrycget(i, 'menu')) # 创建新菜单 new_menu = tk.Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label=menu_name, menu=new_menu) return new_menu except Exception as e: logger.error(f"获取或创建菜单时出错: {e}") # 不返回任何内容,让调用者处理None值 return None def update_display_menu(self): """更新显示菜单""" try: display_menu = self._get_or_create_menu("显示") if not display_menu: return display_menu.delete(0, 'end') # 创建显示模式变量 if not hasattr(self, 'display_mode_var'): self.display_mode_var = tk.StringVar(value=self.display_mode) # 显示模式 display_mode_menu = tk.Menu(display_menu, tearoff=0) display_menu.add_cascade(label="显示模式", menu=display_mode_menu) display_modes = ["固定范围", "帧平均±1000", "帧平均±200", "自定义"] for mode in display_modes: display_mode_menu.add_radiobutton( label=mode, variable=self.display_mode_var, # 修正为变量对象 value=mode, command=lambda m=mode: self.set_display_mode(m) ) file_menu.add_separator() file_menu.add_command(label="导出当前帧为TXT...", command=self.export_to_txt) file_menu.add_separator() file_menu.add_command(label="退出", command=self.root.quit) except Exception as e: logger.error(f"更新文件菜单时出错: {e}") def _update_source_menu(self): """更新数据源菜单""" try: if not self.menubar: return # 查找显示菜单 display_menu = None for i in range(self.menubar.index('end') + 1): if self.menubar.entrycget(i, 'label') == "显示": menu_name = self.menubar.entrycget(i, 'menu') display_menu = self.menubar.nametowidget(menu_name) break if not display_menu: return # 查找数据源菜单项 source_menu = None for i in range(display_menu.index('end') + 1): if display_menu.entrycget(i, 'label') == "数据源": menu_name = display_menu.entrycget(i, 'menu') source_menu = display_menu.nametowidget(menu_name) break if not source_menu: return # 清除并重新添加菜单项 source_menu.delete(0, 'end') source_menu.add_radiobutton( label="原始数据", variable=self.source_var, value="原始数据", command=self.on_source_changed ) if self.operations: source_menu.add_radiobutton( label="运算结果", variable=self.source_var, value="运算结果", command=self.on_source_changed ) except Exception as e: logger.error(f"更新数据源菜单时出错: {e}") def update_formula_menu(self): """更新公式菜单""" try: formula_menu = self._get_or_create_menu("公式") formula_menu.delete(0, 'end') formula_menu.add_command(label="创建公式...", command=self.create_formula) if self.operations: formula_menu.add_separator() operations_menu = tk.Menu(formula_menu, tearoff=0) formula_menu.add_cascade(label="运算结果", menu=operations_menu) for name in sorted(self.operations.keys()): operations_menu.add_command( label=f"{name}: {self.operations[name]['formula']}", command=lambda n=name: self.show_operation_result(n) ) formula_menu.add_separator() formula_menu.add_command(label="保存当前运算结果", command=self.save_operation_result) except Exception as e: logger.error(f"更新公式菜单时出错: {e}") def update_operations_menu(self): """更新运算结果菜单""" try: operations_menu = self._get_or_create_menu("运算结果") operations_menu.delete(0, 'end') if self.operations: for name in sorted(self.operations.keys()): operations_menu.add_command( label=f"{name}: {self.operations[name]['formula']}", command=lambda n=name: self.show_operation_result(n) ) operations_menu.add_separator() operations_menu.add_command( label="保存当前运算结果", command=self.save_operation_result ) except Exception as e: logger.error(f"更新运算结果菜单时出错: {e}") def _update_source_menu(self): """更新数据源菜单""" try: if not self.menubar: return # 查找显示菜单 display_menu_idx = None for i in range(self.menubar.index('end') + 1): if self.menubar.entrycget(i, 'label') == "显示": display_menu_idx = i break if display_menu_idx is None: return display_menu = self.menubar.entrycget(display_menu_idx, 'menu') # 查找数据源菜单项 source_menu_idx = None for i in range(display_menu.index('end') + 1): if display_menu.entrycget(i, 'label') == "数据源": source_menu_idx = i break if source_menu_idx is None: return source_menu = display_menu.entrycget(source_menu_idx, 'menu') # 清除并重新添加菜单项 source_menu.delete(0, 'end') source_menu.add_radiobutton( label="原始数据", variable=self.source_var, value="原始数据", command=self.on_source_changed ) if self.operations: source_menu.add_radiobutton( label="运算结果", variable=self.source_var, value="运算结果", command=self.on_source_changed ) except Exception as e: logger.error(f"更新数据源菜单时出错: {e}") def initialize_ui(self): """初始化用户界面""" self.root = tk.Tk() self.root.title("二进制数据可视化工具") self.root.geometry("1200x800") # 创建主框架 main_frame = tk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True) # 创建控制框架 control_frame = tk.Frame(main_frame, height=50) control_frame.pack(fill=tk.X, side=tk.TOP, padx=10, pady=5) # 数据源切换控件 source_frame = tk.Frame(control_frame) source_frame.pack(fill=tk.X, side=tk.RIGHT, padx=(10, 0), pady=5) tk.Label(source_frame, text="显示源:").pack(side=tk.LEFT, padx=5) self.source_var = tk.StringVar(value="original") source_combo = ttk.Combobox(source_frame, textvariable=self.source_var, width=15) source_combo['values'] = ("原始数据", "运算结果") source_combo.pack(side=tk.LEFT, padx=5) source_combo.bind("<<ComboboxSelected>>", self.on_source_changed) # 创建Matplotlib图形 self.fig, (self.ax_image, self.ax_hist) = plt.subplots(2, 1, figsize=(10, 7), gridspec_kw={'height_ratios': [4, 1]}) self.fig.subplots_adjust(hspace=0.3) # 初始图像 self.current_image = self.ax_image.imshow(np.zeros((self.height, self.width)), cmap='gray') self.ax_image.axis('off') # 初始直方图 self.ax_hist.hist(np.zeros(self.height * self.width), bins=50, alpha=0.7) self.ax_hist.set_title("像素值分布直方图") self.ax_hist.set_xlabel("像素值") self.ax_hist.set_ylabel("频次") self.ax_hist.grid(True) # 创建Canvas self.canvas = FigureCanvasTkAgg(self.fig, master=main_frame) self.canvas.draw() self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 添加Matplotlib工具栏 toolbar = NavigationToolbar2Tk(self.canvas, main_frame) toolbar.update() # 创建帧控制滑块 slider_frame = tk.Frame(main_frame, height=50) slider_frame.pack(fill=tk.X, side=tk.BOTTOM, padx=10, pady=5) # 帧号标签和输入框 tk.Label(slider_frame, text="帧号:").pack(side=tk.LEFT, padx=5) # 创建Matplotlib文本框用于输入帧号 self.textbox_frame = TextBox( plt.axes([0.15, 0.01, 0.05, 0.04]), '', initial="1", color='white', hovercolor='lightgoldenrodyellow' ) self.textbox_frame.on_submit(self.on_frame_number_submit) # 创建Matplotlib滑块用于控制帧 self.slider_frame = Slider( plt.axes([0.25, 0.01, 0.65, 0.04]), '帧', 0, 100, # 初始范围,会在加载文件后更新 valinit=0, valstep=1 ) self.slider_frame.on_changed(self.on_frame_change) self.slider_frame.set_active(False) # 初始禁用,直到加载文件 # 初始化菜单 self.initialize_menus() # 更新菜单状态 self.update_file_menu() self.update_display_menu() self.update_formula_menu() # 设置数据源菜单 self._update_source_menu() # 显示欢迎信息 self.fig.suptitle("欢迎使用二进制数据可视化工具\n请从文件菜单打开二进制数据文件") self.canvas.draw() def initialize_menus(self): """初始化菜单栏""" if not self.menubar: self.menubar = tk.Menu(self.root) self.root.config(menu=self.menubar) def run(self): """运行应用程序""" self.initialize_ui() self.root.mainloop() if __name__ == "__main__": app = BinaryVisualizer() app.run() 提示Traceback (most recent call last): File "E:\zlt_work\zlt_work\DATA\读取多文件测试.py", line 1185, in <module> app.run() File "E:\zlt_work\zlt_work\DATA\读取多文件测试.py", line 1180, in run self.initialize_ui() File "E:\zlt_work\zlt_work\DATA\读取多文件测试.py", line 1161, in initialize_ui self.update_file_menu() AttributeError: 'BinaryVisualizer' object has no attribute 'update_file_menu'
06-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值