<s:textfield>标签的tr问题

本文介绍了解决Struts2中使用s:textfield标签在表格单元格内显示时自动换行的问题。通过修改struts.ui.theme配置为simple来避免占位过大的情况。

在用s:textfield标签显示action中返回的值,用name属性即可。但是如果放在table中的td的话,就会出现一个问题。自动换行,在页面上看起来就是占了一个tr上下距离比较高。这个原因是标签定义的时候就定义成这样了。看看怎么定义的:

查看struts.properties或$ {struts-core-2.06.jar}/org/apache/struts2/default.properties文件,其中有如下配置: 
struts.ui.theme=xhtml 
struts.ui.templateDir=template 
struts.ui.templateSuffix=ftl 

修改成这样

struts.ui.theme=simple 
struts.ui.templateDir=template 
struts.ui.templateSuffix=ftl 

这样修改的话能解决问题,但是要修改jar包总是不好的,建议用下面的方式修改:

在struts.xml文件中加入下面的代码即可;

<constant name="struts.ui.theme" value="simple" /> 
<constant name="struts.ui.templateDir" value="template" /> 
<constant name="struts.ui.templateSuffix" value="ftl" /> 


// src/components/UnifiedCsvMerger.jsx import React, { useState, useRef } from "react"; import Papa from "papaparse"; import { processDataList, generateTotalStats, filterPlayIdWithSuccessPriority } from "./utils"; import "./index.css"; // 引入样式 const UnifiedCsvMerger = () => { const fileInputRef = useRef(null); const [selectedFiles, setSelectedFiles] = useState([]); const [mergedPreview, setMergedPreview] = useState([]); // 缓存处理后的数据 const [processedData, setProcessedData] = useState([]); const [statsSummary, setStatsSummary] = useState([]); const [statsSummaryUserImpact, setStatsSummaryUserImpact] = useState([]); const [totalRows, setTotalRows] = useState(null); const [isParsing, setIsParsing] = useState(false); const [error, setError] = useState(null); // 表单状态 const [formValues, setFormValues] = useState({ startTime: "", endTime: "", excludeErrorCodes: "-71114\n-71101\n-52208\n-1007\n-81607\n-80301\n-52405\n-1033\n-1029\n2\n-52407", targetEid: "Liveview.Video.StartPlay", }); // 单选选项 const options = [ { value: "res", label: "明细数据 (res)" }, { value: "err_summary", label: "错误统计汇总 (err_summary)" }, { value: "err_summary_user_impact", label: "错误统计汇总 (用户感知维度)" }, ]; const [selectedOption, setSelectedOption] = useState("res"); // 添加文件 const handleAddFiles = () => { const files = fileInputRef.current?.files; if (!files || files.length === 0) return; const newFiles = Array.from(files).filter((file) => /\.(csv)$/i.test(file.name)); if (newFiles.length === 0) { alert("请选择有效的 CSV 文件"); return; } setSelectedFiles((prev) => [...prev, ...newFiles]); if (fileInputRef.current) fileInputRef.current.value = ""; }; // 删除文件 const handleRemoveFile = (index) => { setSelectedFiles((prev) => prev.filter((_, i) => i !== index)); }; // 处理表单输入 const handleInputChange = (e) => { const { name, value } = e.target; setFormValues((prev) => ({ ...prev, [name]: value, })); }; // 解析所有文件 const handleParseAll = () => { if (selectedFiles.length === 0) { setError("请先添加至少一个 CSV 文件"); return; } setIsParsing(true); setError(null); setMergedPreview([]); setTotalRows(0); const promises = selectedFiles.map( (file) => new Promise((resolve) => { const rows = []; Papa.parse(file, { worker: false, header: true, skipEmptyLines: true, transform: (value) => value.trim(), step: (result) => { rows.push(result.data); }, complete: () => resolve(rows), error: () => resolve([]), }); }) ); Promise.all(promises) .then((allRows) => { const mergedData = allRows.flat(); // 构造配置项 const config = { start_time: formValues.startTime.trim() || undefined, end_time: formValues.endTime.trim() || undefined, exclude_error_codes: formValues.excludeErrorCodes .split(/[\n,;,\s]+/) .map((code) => code.trim()) .filter((code) => code !== ""), target_eid: formValues.targetEid.trim() || "Liveview.Video.StartPlay", }; // 核心处理 const res = processDataList(mergedData, config); const err_summary = generateTotalStats(res); const err_summary_user_impact = generateTotalStats(filterPlayIdWithSuccessPriority(res)); // 缓存结果供切换使用 setProcessedData(res); setStatsSummary(err_summary); setStatsSummaryUserImpact(err_summary_user_impact); // 默认显示明细数据前100条 setMergedPreview(res.slice(0, 100)); setTotalRows(mergedData.length); }) .catch((err) => { const message = err instanceof Error ? err.message : "未知错误"; setError(`解析过程中发生错误: ${message}`); }) .finally(() => { setIsParsing(false); }); }; // 单选按钮变化 const handleRadioChange = (e) => { const value = e.target.value; setSelectedOption(value); if (value === "res") { setMergedPreview(processedData.slice(0, 100)); } else if (value === "err_summary") { setMergedPreview(statsSummary); } else if (value === "err_summary_user_impact") { setMergedPreview(statsSummaryUserImpact); } }; return ( <div className="unified-csv-merger"> <h2>📁 解析多个 CSV 文件(统一格式)</h2> {/* 添加文件 */} <div className="action-section"> <input ref={fileInputRef} type="file" accept=".csv" multiple onChange={handleAddFiles} disabled={isParsing} /> <button onClick={handleAddFiles} disabled={isParsing} className="add-btn"> 添加文件 </button> </div> {/* 已选文件列表 */} {selectedFiles.length > 0 && ( <div className="file-list"> <h3>📎 已选文件 ({selectedFiles.length}):</h3> <ul> {selectedFiles.map((file, index) => ( <li key={index}> <span title={file.name}> {file.name} <small>({(file.size / 1024).toFixed(1)} KB)</small> </span> <button onClick={() => handleRemoveFile(index)} disabled={isParsing} className="remove-btn"> 删除 </button> </li> ))} </ul> </div> )} {/* 过滤条件表单 */} <div className="filter-form"> <h3>⚙️ 数据处理配置</h3> <div className="form-group"> <label> 开始时间(可选) <input type="datetime-local" name="startTime" value={formValues.startTime} onChange={handleInputChange} className="form-control" /> </label> </div> <div className="form-group"> <label> 结束时间(可选) <input type="datetime-local" name="endTime" value={formValues.endTime} onChange={handleInputChange} className="form-control" /> </label> </div> <div className="form-group"> <label> 排除的错误码(每行一个,支持逗号/空格分隔) <textarea name="excludeErrorCodes" value={formValues.excludeErrorCodes} onChange={handleInputChange} rows={6} placeholder="例如: -71114 -1007 ..." className="form-control" /> </label> </div> <div className="form-group"> <label> 目标事件类型(target_eid) <input type="text" name="targetEid" value={formValues.targetEid} onChange={handleInputChange} placeholder="如:Liveview.Video.StartPlay" className="form-control" /> </label> </div> </div> {/* 解析按钮 */} <button onClick={handleParseAll} disabled={isParsing || selectedFiles.length === 0} className="parse-btn"> {isParsing ? "解析中..." : "🚀 解析并合并所有文件"} </button> {/* 错误提示 */} {error && <div className="error-message">{error}</div>} {/* 合并结果展示 */} {!isParsing && totalRows !== null && ( <div className="result-preview"> {/* 单选切换 */} <div className="radio-options"> <h3>请选择预览内容:</h3> {options.map((option) => ( <label key={option.value} style={{ display: "block", margin: "8px 0" }}> <input type="radio" value={option.value} checked={selectedOption === option.value} onChange={handleRadioChange} /> {option.label} </label> ))} <p> 当前显示: <strong>{options.find((o) => o.value === selectedOption)?.label}</strong> </p> </div> <h3>📊 预览结果</h3> <p> <strong>原始共 {totalRows.toLocaleString()} 行数据</strong>, 当前展示为 {selectedOption === "res" ? "过滤后明细数据前100条" : "错误统计汇总"} </p> <div className="table-container"> <table> <thead> <tr> {Object.keys(mergedPreview[0] || {}).map((header) => ( <th key={header}>{header}</th> ))} </tr> </thead> <tbody> {mergedPreview.length > 0 ? ( mergedPreview.map((row, idx) => ( <tr key={idx}> {Object.values(row).map((cell, j) => ( <td key={j}>{cell}</td> ))} </tr> )) ) : ( <tr> <td colSpan={Object.keys(mergedPreview[0] || {}).length || 1} className="empty-row"> 暂无数据 </td> </tr> )} </tbody> </table> </div> </div> )} </div> ); }; export default UnifiedCsvMerger; 使用mui组件
最新发布
10-21
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值