Browse Source

一時保存

liuxf 2 months ago
parent
commit
8b7336a19d

+ 59 - 16
new-react-admin-ui/src/pages/estate/estateList/EstateExport.module.css

@@ -27,7 +27,7 @@
   margin-bottom: 16px;
 }
 
-/* 导出操作区 */
+/* 导出操作区 - 核心对齐优化 */
 .exportSection {
   margin-bottom: 32px;
   padding: 16px;
@@ -35,31 +35,47 @@
   border-radius: 8px;
 }
 
+/* 表单容器:使用网格布局实现标签与控件对齐 */
 .formGroup {
-  display: flex;
-  align-items: center;
-  gap: 24px;
-  flex-wrap: wrap;
+  display: grid;
+  grid-template-columns: auto 1fr; /* 第一列自适应标签宽度,第二列放控件 */
+  row-gap: 12px; /* 两行之间的间距 */
+  column-gap: 24px; /* 标签与控件的间距 */
+  padding: 8px 0;
 }
 
-.formItem {
-  display: flex;
-  flex-direction: column;
-  gap: 8px;
+/* 每行容器(基准日/出力内容) */
+.formRow {
+  display: contents; /* 让子元素直接参与网格布局,消除层级干扰 */
 }
 
+/* 标签样式:统一左对齐,固定在第一列 */
 .label {
+  grid-column: 1; /* 强制放在第一列 */
   font-size: 14px;
   color: #555;
   font-weight: 500;
+  line-height: 36px; /* 与控件高度一致,实现垂直居中 */
+  text-align: left; /* 确保左对齐 */
+  min-width: 80px; /* 避免标签过短导致控件偏移 */
 }
 
+/* 控件容器:统一左对齐,固定在第二列 */
+.control {
+  grid-column: 2; /* 强制放在第二列 */
+  display: flex;
+  align-items: center; /* 控件内部垂直居中 */
+}
+
+/* 输入框样式 */
 .input {
   padding: 8px 12px;
   border: 1px solid #ddd;
   border-radius: 4px;
   font-size: 14px;
   width: 160px;
+  height: 36px; /* 固定高度,与radio组对齐 */
+  box-sizing: border-box;
 }
 
 .input:focus {
@@ -68,9 +84,12 @@
   box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.2);
 }
 
+/* Radio组样式 */
 .radioGroup {
   display: flex;
   gap: 16px;
+  height: 36px; /* 与输入框高度一致 */
+  align-items: center; /* radio按钮垂直居中 */
 }
 
 .radioItem {
@@ -86,6 +105,14 @@
   cursor: pointer;
 }
 
+/* 按钮区域:单独一行,右对齐 */
+.buttonRow {
+  grid-column: 1 / -1; /* 跨两列 */
+  display: flex;
+  justify-content: flex-end; /* 靠右对齐 */
+  margin-top: 16px; /* 与上方内容保持间距 */
+}
+
 .exportBtn {
   padding: 8px 24px;
   background-color: #4299e1;
@@ -96,6 +123,8 @@
   font-weight: 500;
   cursor: pointer;
   transition: background-color 0.2s;
+  height: 36px; /* 与输入框高度一致 */
+  box-sizing: border-box;
 }
 
 .exportBtn:hover {
@@ -105,6 +134,7 @@
 .disabledBtn {
   background-color: #ccc;
   cursor: not-allowed;
+  transition: none;
 }
 
 /* 历史列表区 */
@@ -123,9 +153,9 @@
   --ag-row-height: 40px;
   --ag-cell-font-size: 14px;
   --ag-cell-padding: 0 12px;
-  --ag-header-height: 48px; /* 增加表头行高(默认可能偏小) */
-  --ag-header-cell-padding: 0 12px; /* 调整表头内边距(上下设为0避免挤压) */
-  --ag-header-line-height: 1.5; /* 文字行高,避免文字本身被截断  */
+  --ag-header-height: 48px; /* 确保表头文字完整显示 */
+  --ag-header-cell-padding: 0 12px;
+  --ag-header-line-height: 1.5;
 }
 
 /* 加载状态 */
@@ -153,15 +183,28 @@
 /* 响应式调整 */
 @media (max-width: 768px) {
   .formGroup {
-    gap: 16px;
+    grid-template-columns: 1fr; /* 移动端改为单列布局 */
+    column-gap: 0;
+  }
+
+  .label, .control {
+    grid-column: 1; /* 所有元素都在第一列 */
+  }
+
+  .label {
+    line-height: 1.5; /* 标签单独成行时不用强制行高 */
+    margin-bottom: 4px;
+  }
+
+  .input, .radioGroup {
+    width: 100%; /* 控件占满宽度 */
   }
 
-  .input {
-    width: 140px;
+  .buttonRow {
+    margin-top: 12px;
   }
 
   .exportBtn {
     width: 100%;
-    margin-top: 8px;
   }
 }

+ 116 - 127
new-react-admin-ui/src/pages/estate/estateList/index.tsx

@@ -8,9 +8,9 @@ import {
   ValueFormatterParams,
   GridReadyEvent,
 } from 'ag-grid-community';
-import styles from './EstateExport.module.css'; // 改为 CSS Modules
+import styles from './EstateExport.module.css';
 
-// 1. 类型定义(补全接口,确保类型安全)
+// 类型定义
 export interface IEstateExportHistory {
   id: string;
   exportDate: string; // yyyymmdd 格式
@@ -18,13 +18,13 @@ export interface IEstateExportHistory {
   exportContent: string; // 1:物件マスタCSV, 2:分析用CSV
 }
 
-// 2. AG Grid 模块注册(保持原有逻辑,补充注释)
+// AG Grid 模块注册
 ModuleRegistry.registerModules([
-  PaginationModule,       // 分页模块
-  ClientSideRowModelModule // 客户端行模型(本地数据分页)
+  PaginationModule,
+  ClientSideRowModelModule
 ]);
 
-// 3. 枚举与映射配置(保持原有,补充类型约束)
+// 枚举与映射配置
 export enum ExportContentType {
   PROPERTY_MASTER = '1',
   ANALYSIS = '2'
@@ -35,18 +35,13 @@ const EXPORT_CONTENT_MAP: Record<ExportContentType, string> = {
   [ExportContentType.ANALYSIS]: '分析用CSV'
 };
 
-// 4. 工具函数(抽离通用逻辑,提高复用性)
-/**
- * 日期格式验证(yyyymmdd)
- * @param date - 待验证日期字符串
- * @returns 验证结果
- */
+// 工具函数
 const validateDate = (date: string): boolean => {
   const regex = /^\d{8}$/;
   if (!regex.test(date)) return false;
 
   const year = parseInt(date.substring(0, 4), 10);
-  const month = parseInt(date.substring(4, 6), 10) - 1; // 月份从 0 开始
+  const month = parseInt(date.substring(4, 6), 10) - 1;
   const day = parseInt(date.substring(6, 8), 10);
 
   const validDate = new Date(year, month, day);
@@ -57,28 +52,23 @@ const validateDate = (date: string): boolean => {
   );
 };
 
-/**
- * 格式化日期显示(yyyymmdd → yyyy/mm/dd)
- * @param date - 原始日期字符串
- * @returns 格式化后的日期
- */
 const formatDisplayDate = (date: string): string => {
   if (!date || date.length !== 8) return '';
   return `${date.slice(0, 4)}/${date.slice(4, 6)}/${date.slice(6, 8)}`;
 };
 
 const EstateExportHistory: React.FC = () => {
-  // 5. 状态管理(补充类型约束,初始化更合理)
+  // 状态管理
   const [baseDate, setBaseDate] = useState<string>('');
   const [selectedContent, setSelectedContent] = useState<ExportContentType>(
     ExportContentType.ANALYSIS
   );
   const [historyList, setHistoryList] = useState<IEstateExportHistory[]>([]);
   const [isExporting, setIsExporting] = useState<boolean>(false);
-  const [gridApi, setGridApi] = useState<any>(null); // AG Grid API 实例
-  const [isLoading, setIsLoading] = useState<boolean>(true); // 列表加载状态
+  const [gridApi, setGridApi] = useState<any>(null);
+  const [isLoading, setIsLoading] = useState<boolean>(true);
 
-  // 6. 样式配置(使用 CSS Modules,避免全局污染)
+  // 样式配置
   const containerStyle = useMemo(() => ({
     width: '100%',
     height: '300px',
@@ -86,20 +76,20 @@ const EstateExportHistory: React.FC = () => {
     overflow: 'hidden'
   }), []);
 
-  // 7. AG Grid 配置(优化默认列配置,补充缺失功能)
+  // AG Grid 配置
   const defaultColDef = useMemo<ColDef>(() => ({
     flex: 1,
     minWidth: 120,
-    sortable: true, // 开启排序(提升体验)
-    filter: true,   // 开启筛选(提升体验)
+    sortable: true,
+    filter: true,
     resizable: true,
-    menuTabs: ['filterMenuTab'], // 只显示筛选菜单(简化操作)
-    cellStyle: { display: 'flex', alignItems: 'center' } // 单元格垂直居中
+    menuTabs: ['filterMenuTab'],
+    cellStyle: { display: 'flex', alignItems: 'center' }
   }), []);
 
-  const paginationPageSize = useMemo(() => 5, []); // 调整为每页 5 条(更合理)
+  const paginationPageSize = useMemo(() => 5, []);
 
-  // 8. 列定义(优化格式化逻辑,抽离为独立函数)
+  // 列定义
   const columnDefs = useMemo<ColDef<IEstateExportHistory>[]>(() => [
     {
       headerName: '出力日付',
@@ -107,14 +97,14 @@ const EstateExportHistory: React.FC = () => {
       flex: 1.2,
       valueFormatter: (params: ValueFormatterParams<IEstateExportHistory>) =>
         formatDisplayDate(params.value),
-      sort: 'desc' // 默认按日期降序排序
+      sort: 'desc'
     },
     {
       headerName: '担当者',
       field: 'exporter',
       flex: 1,
       filter: 'agTextColumnFilter',
-      filterParams: { matchContains: true } // 包含匹配(更灵活)
+      filterParams: { matchContains: true }
     },
     {
       headerName: '出力内容',
@@ -122,7 +112,7 @@ const EstateExportHistory: React.FC = () => {
       flex: 1.5,
       valueFormatter: (params: ValueFormatterParams<IEstateExportHistory>) =>
         EXPORT_CONTENT_MAP[params.value as ExportContentType] || '不明',
-      filter: 'agSetColumnFilter', // 下拉筛选(更直观)
+      filter: 'agSetColumnFilter',
       filterParams: {
         values: Object.values(ExportContentType).map(key =>
           EXPORT_CONTENT_MAP[key as ExportContentType]
@@ -133,11 +123,10 @@ const EstateExportHistory: React.FC = () => {
     }
   ], []);
 
-  // 9. 初始化数据(优化加载状态,模拟真实接口延迟)
+  // 初始化数据
   useEffect(() => {
     const fetchHistory = async () => {
       try {
-        // 模拟接口请求延迟
         await new Promise(resolve => setTimeout(resolve, 600));
 
         const mockBackendData: IEstateExportHistory[] = [
@@ -165,16 +154,15 @@ const EstateExportHistory: React.FC = () => {
     fetchHistory();
   }, []);
 
-  // 10. Grid 初始化回调(保存 API 实例,用于后续操作)
+  // Grid 初始化回调
   const onGridReady = useCallback((params: GridReadyEvent<IEstateExportHistory>) => {
     setGridApi(params.api);
   }, []);
 
-  // 11. 导出处理逻辑(优化异步流程,增强用户反馈)
+  // 导出处理逻辑
   const handleExport = useCallback(async () => {
     if (isExporting) return;
 
-    // 验证基準日
     if (!baseDate) {
       alert('基準日を入力してください');
       return;
@@ -188,7 +176,6 @@ const EstateExportHistory: React.FC = () => {
     setIsExporting(true);
 
     try {
-      // 模拟接口请求(实际项目中替换为 axios/fetch)
       await new Promise(resolve => setTimeout(resolve, 800));
 
       const newHistory: IEstateExportHistory = {
@@ -198,12 +185,10 @@ const EstateExportHistory: React.FC = () => {
         exportContent: selectedContent
       };
 
-      // 新增数据添加到列表顶部,并保持排序
       setHistoryList(prev => [newHistory, ...prev].sort((a, b) =>
         b.exportDate.localeCompare(a.exportDate)
       ));
 
-      // 刷新 Grid 视图
       gridApi?.refreshCells();
       alert('エクスポートが完了しました');
     } catch (error) {
@@ -215,107 +200,111 @@ const EstateExportHistory: React.FC = () => {
     }
   }, [baseDate, selectedContent, isExporting, gridApi]);
 
-  // 12. 日期输入处理(限制输入格式,增强用户体验)
+  // 日期输入处理
   const handleDateChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
-    // 只允许输入数字,且长度不超过 8
     const value = e.target.value.replace(/\D/g, '').slice(0, 8);
     setBaseDate(value);
   }, []);
 
   return (
-    <div className={styles.container}>
-      <h2 className={styles.title}>エクスポート</h2>
-
-      {/* 导出操作区 */}
-      <div className={styles.exportSection}>
-        <h3 className={styles.subtitle}>エクスポート操作</h3>
-        <div className={styles.formGroup}>
-          <div className={styles.formItem}>
-            <label className={styles.label}>基準日</label>
-            <input
-              type="text"
-              className={styles.input}
-              placeholder="yyyymmdd"
-              value={baseDate}
-              onChange={handleDateChange}
-              maxLength={8}
-              disabled={isExporting}
-              aria-label="基準日入力"
-            />
-          </div>
-
-          <div className={styles.formItem}>
-            <label className={styles.label}>出力内容</label>
-            <div className={styles.radioGroup}>
-              <label className={styles.radioItem}>
+      <div className={styles.container}>
+        <h2 className={styles.title}>エクスポート</h2>
+
+        <div className={styles.exportSection}>
+          <h3 className={styles.subtitle}>エクスポート操作</h3>
+          {/* 网格布局容器 */}
+          <div className={styles.formGroup}>
+            {/* 基准日行:标签 + 输入框 */}
+            <div className={styles.formRow}>
+              <label className={styles.label}>基準日</label>
+              <div className={styles.control}>
                 <input
-                  type="radio"
-                  name="exportContent"
-                  value={ExportContentType.PROPERTY_MASTER}
-                  checked={selectedContent === ExportContentType.PROPERTY_MASTER}
-                  onChange={() => setSelectedContent(ExportContentType.PROPERTY_MASTER)}
-                  disabled={isExporting}
+                    type="text"
+                    className={styles.input}
+                    placeholder="yyyymmdd"
+                    value={baseDate}
+                    onChange={handleDateChange}
+                    maxLength={8}
+                    disabled={isExporting}
                 />
-                <span className={styles.radioLabel}>物件マスタCSV</span>
-              </label>
-              <label className={styles.radioItem}>
-                <input
-                  type="radio"
-                  name="exportContent"
-                  value={ExportContentType.ANALYSIS}
-                  checked={selectedContent === ExportContentType.ANALYSIS}
-                  onChange={() => setSelectedContent(ExportContentType.ANALYSIS)}
+              </div>
+            </div>
+
+            {/* 出力内容行:标签 + radio组 */}
+            <div className={styles.formRow}>
+              <label className={styles.label}>出力内容</label>
+              <div className={styles.control}>
+                <div className={styles.radioGroup}>
+                  <label className={styles.radioItem}>
+                    <input
+                        type="radio"
+                        name="exportContent"
+                        value={ExportContentType.PROPERTY_MASTER}
+                        checked={selectedContent === ExportContentType.PROPERTY_MASTER}
+                        onChange={() => setSelectedContent(ExportContentType.PROPERTY_MASTER)}
+                        disabled={isExporting}
+                    />
+                    <span className={styles.radioLabel}>物件マスタCSV</span>
+                  </label>
+                  <label className={styles.radioItem}>
+                    <input
+                        type="radio"
+                        name="exportContent"
+                        value={ExportContentType.ANALYSIS}
+                        checked={selectedContent === ExportContentType.ANALYSIS}
+                        onChange={() => setSelectedContent(ExportContentType.ANALYSIS)}
+                        disabled={isExporting}
+                    />
+                    <span className={styles.radioLabel}>分析用CSV</span>
+                  </label>
+                </div>
+              </div>
+            </div>
+
+            {/* 按钮行 */}
+            <div className={styles.buttonRow}>
+              <button
+                  className={`${styles.exportBtn} ${isExporting ? styles.disabledBtn : ''}`}
+                  onClick={handleExport}
                   disabled={isExporting}
-                />
-                <span className={styles.radioLabel}>分析用CSV</span>
-              </label>
+              >
+                {isExporting ? '処理中...' : '出力'}
+              </button>
             </div>
           </div>
-
-          <button
-            className={`${styles.exportBtn} ${isExporting ? styles.disabledBtn : ''}`}
-            onClick={handleExport}
-            disabled={isExporting}
-          >
-            {isExporting ? '処理中...' : '出力'}
-          </button>
         </div>
-      </div>
 
-      {/* 导出历史区 */}
-      <div className={styles.historySection}>
-        <h3 className={styles.subtitle}>出力履歴</h3>
-        <div className={styles.underline}></div>
-
-        {isLoading ? (
-          // 加载状态
-          <div className={styles.loading}>
-            <span>履歴データを読み込んでいます...</span>
-          </div>
-        ) : historyList.length === 0 ? (
-          // 空状态
-          <div className={styles.emptyState}>
-            <span>出力履歴がありません</span>
-          </div>
-        ) : (
-          // AG Grid 表格
-          <div className={`ag-theme-alpine ${styles.gridContainer}`} style={containerStyle}>
-            <AgGridReact<IEstateExportHistory>
-              rowData={historyList}
-              columnDefs={columnDefs}
-              defaultColDef={defaultColDef}
-              pagination={true}
-              paginationPageSize={paginationPageSize}
-              paginationPageSizeSelector={[5, 10, 20]}
-              onGridReady={onGridReady}
-              animateRows={true}
-              suppressDragLeaveHidesColumns={true}
-              enableCellTextSelection={true}
-            />
-          </div>
-        )}
+        {/* 导出历史区 */}
+        <div className={styles.historySection}>
+          <h3 className={styles.subtitle}>出力履歴</h3>
+          <div className={styles.underline}></div>
+
+          {isLoading ? (
+              <div className={styles.loading}>
+                <span>履歴データを読み込んでいます...</span>
+              </div>
+          ) : historyList.length === 0 ? (
+              <div className={styles.emptyState}>
+                <span>出力履歴がありません</span>
+              </div>
+          ) : (
+              <div className={`ag-theme-alpine ${styles.gridContainer}`} style={containerStyle}>
+                <AgGridReact<IEstateExportHistory>
+                    rowData={historyList}
+                    columnDefs={columnDefs}
+                    defaultColDef={defaultColDef}
+                    pagination={true}
+                    paginationPageSize={paginationPageSize}
+                    paginationPageSizeSelector={[5, 10, 20]}
+                    onGridReady={onGridReady}
+                    animateRows={true}
+                    suppressDragLeaveHidesColumns={true}
+                    enableCellTextSelection={true}
+                />
+              </div>
+          )}
+        </div>
       </div>
-    </div>
   );
 };