quyx@nextosd.com 1 ヶ月 前
コミット
b4ff0db3be

ファイルの差分が大きいため隠しています
+ 1026 - 0
new-react-admin-ui/package-lock.json


+ 2 - 0
new-react-admin-ui/package.json

@@ -16,8 +16,10 @@
     "@mui/icons-material": "^6.1.9",
     "@mui/material": "^6.1.9",
     "@mui/x-tree-view": "^8.16.0",
+    "@types/antd": "^0.12.32",
     "ag-grid-enterprise": "^34.3.1",
     "ag-grid-react": "^34.3.1",
+    "antd": "^5.28.1",
     "react": "^18.3.1",
     "react-admin": "^5.12.2",
     "react-dom": "^18.3.1",

+ 5 - 0
new-react-admin-ui/src/App.tsx

@@ -5,6 +5,7 @@ import { ruoyiDataProvider } from '@/adapters/ruoyiDataProvider';
 import { ruoyiAuthProvider } from '@/adapters/ruoyiAuthProvider';
 import { LoginPage } from '@/pages/LoginPage';
 import { UserList } from '@/pages/users/UserList';
+import { LabelGenerationList } from '@/pages/generations/LabelGenerationList';
 import { UserEdit } from '@/pages/users/UserEdit';
 import { UserCreate } from '@/pages/users/UserCreate';
 import { RoleList } from '@/pages/roles/RoleList';
@@ -70,6 +71,10 @@ export const App = () => {
           name="system/dashboards"
           list={DashboardList}
         />
+        <Resource
+          name="label/generations"
+          list={LabelGenerationList}
+        />
         
         <CustomRoutes noLayout>
           <Route path="/error" element={<ErrorPage />} />

+ 17 - 69
new-react-admin-ui/src/adapters/ruoyiDataProvider.ts

@@ -113,21 +113,17 @@ const request = async <T>(url: string, options: RequestInit & { isToken?: boolea
     
     // 若依后台返回格式处理
     if (data.code !== 200) {
-      // 对于权限错误(403),保存错误信息并重定向到错误页面
-      if (data.code === 403) {
-        const errorInfo = {
-          status: data.code,
-          message: data.msg || '权限不足',
-          resource: url,
-          action: options.method || 'GET'
-        };
-        sessionStorage.setItem('errorInfo', JSON.stringify(errorInfo));
-        
-        // 重定向到错误页面
-        window.location.href = '/error';
-      }
+      // 保存错误信息到sessionStorage
+      const errorInfo = {
+        status: data.code,
+        message: data.msg || '请求失败',
+        resource: url,
+        action: options.method || 'GET'
+      };
+      sessionStorage.setItem('errorInfo', JSON.stringify(errorInfo));
       
-      // 对于其他错误,直接抛出异常,让React Admin处理
+      // 重定向到错误页面
+      window.location.href = '/error';
       throw new Error(data.msg || '请求失败');
     }
 
@@ -156,14 +152,6 @@ export const ruoyiDataProvider: RuoyiDataProvider = {
     const { page = 1, perPage = 10 } = params.pagination || {};
     const { field, order } = params.sort || {};
     
-    // 解析层级化的资源名称,例如 "system/users" -> "users"
-    const getFlatResourceName = (resource: string): string => {
-      const parts = resource.split('/');
-      return parts[parts.length - 1]; // 取最后一部分作为扁平化资源名
-    };
-    
-    const flatResource = getFlatResourceName(resource);
-
     // 映射排序字段:将React Admin的默认字段名映射为若依后端的字段名
     const getMappedSortField = (resource: string, field: string) => {
       const fieldMappings: Record<string, Record<string, string>> = {
@@ -179,7 +167,7 @@ export const ruoyiDataProvider: RuoyiDataProvider = {
       return fieldMappings[resource]?.[field] || field;
     };
     
-    const mappedField = field ? getMappedSortField(flatResource, field) : undefined;
+    const mappedField = field ? getMappedSortField(resource, field) : undefined;
     
     const queryParams = new URLSearchParams({
       pageNum: page.toString(),
@@ -188,7 +176,7 @@ export const ruoyiDataProvider: RuoyiDataProvider = {
       ...params.filter,
     });
 
-    switch (flatResource) {
+    switch (resource) {
       case 'users': {
         const data = await request<UserPageResult>(`/system/user/list?${queryParams}`);
         // 将userId映射为id,以满足React Admin的数据格式要求
@@ -294,15 +282,7 @@ export const ruoyiDataProvider: RuoyiDataProvider = {
 
   // 获取单条记录
   getOne: async (resource: string, params: GetOneParams): Promise<GetOneResult> => {
-    // 解析层级化的资源名称
-    const getFlatResourceName = (resource: string): string => {
-      const parts = resource.split('/');
-      return parts[parts.length - 1];
-    };
-    
-    const flatResource = getFlatResourceName(resource);
-    
-    switch (flatResource) {
+    switch (resource) {
       case 'users': {
         const data = await request<any>(`/system/user/${params.id}`);
         // 将userId映射为id,以满足React Admin的数据格式要求
@@ -356,15 +336,7 @@ export const ruoyiDataProvider: RuoyiDataProvider = {
 
   // 创建记录
   create: async (resource: string, params: CreateParams): Promise<CreateResult> => {
-    // 解析层级化的资源名称
-    const getFlatResourceName = (resource: string): string => {
-      const parts = resource.split('/');
-      return parts[parts.length - 1];
-    };
-    
-    const flatResource = getFlatResourceName(resource);
-    
-    switch (flatResource) {
+    switch (resource) {
       case 'users': {
         // 确保roleIds参数以数组格式发送
         const requestData = {
@@ -409,15 +381,7 @@ export const ruoyiDataProvider: RuoyiDataProvider = {
 
   // 更新记录
   update: async (resource: string, params: UpdateParams): Promise<UpdateResult> => {
-    // 解析层级化的资源名称
-    const getFlatResourceName = (resource: string): string => {
-      const parts = resource.split('/');
-      return parts[parts.length - 1];
-    };
-    
-    const flatResource = getFlatResourceName(resource);
-    
-    switch (flatResource) {
+    switch (resource) {
       case 'users': {
         // 确保roleIds参数以数组格式发送
         const requestData = {
@@ -465,15 +429,7 @@ export const ruoyiDataProvider: RuoyiDataProvider = {
 
   // 删除记录
   delete: async (resource: string, params: DeleteParams): Promise<DeleteResult> => {
-    // 解析层级化的资源名称
-    const getFlatResourceName = (resource: string): string => {
-      const parts = resource.split('/');
-      return parts[parts.length - 1];
-    };
-    
-    const flatResource = getFlatResourceName(resource);
-    
-    switch (flatResource) {
+    switch (resource) {
       case 'users': {
         await request<Result>(`/system/user/${params.id}`, {
           method: 'DELETE',
@@ -510,15 +466,7 @@ export const ruoyiDataProvider: RuoyiDataProvider = {
 
   // 批量获取记录 - 由于后台没有批量接口,通过循环调用单个接口实现
   getMany: async (resource: string, params: GetManyParams): Promise<GetManyResult> => {
-    // 解析层级化的资源名称
-    const getFlatResourceName = (resource: string): string => {
-      const parts = resource.split('/');
-      return parts[parts.length - 1];
-    };
-    
-    const flatResource = getFlatResourceName(resource);
-    
-    switch (flatResource) {
+    switch (resource) {
       case 'users': {
         // 循环调用单个用户接口获取多个用户信息
         const promises = params.ids.map(id => 

+ 553 - 0
new-react-admin-ui/src/pages/generations/LabelGenerationList.tsx

@@ -0,0 +1,553 @@
+import React, { useMemo, useState, useEffect } from "react";
+import { AgGridReact } from "ag-grid-react";
+import {
+  ClientSideRowModelModule,
+  ColDef,
+  ColGroupDef,
+  ModuleRegistry,
+  ICellRendererParams,
+  SelectEditorModule,
+  CellStyleModule
+} from "ag-grid-community";
+import "ag-grid-community/styles/ag-theme-alpine.css";
+import { DatePicker } from 'antd';
+import dayjs, { Dayjs } from 'dayjs';
+import { useDataProvider } from 'react-admin';
+import { useParams } from 'react-router-dom';
+
+// 注册AG Grid模块
+ModuleRegistry.registerModules([
+  ClientSideRowModelModule,
+  SelectEditorModule,
+  CellStyleModule
+]);
+
+// 部名选项
+const deptOptions = [
+  { value: "", label: "選択しない" },
+  { value: "営業部", label: "営業部" },
+  { value: "物流部", label: "物流部" },
+  { value: "管理部", label: "管理部" },
+  { value: "技術部", label: "技術部" },
+  { value: "人事部", label: "人事部" },
+  { value: "販売第一部", label: "販売第一部" },
+];
+
+// 地区名选项
+const areaOptions = [
+  { value: "", label: "選択しない" },
+  { value: "東  部", label: "東  部" },
+  { value: "西  部", label: "西  部" },
+  { value: "南  部", label: "南  部" },
+  { value: "北  部", label: "北  部" },
+  { value: "中  部", label: "中  部" }
+];
+
+// 時台选项
+const hourOptions = [
+  { value: "", label: "選択しない" },
+  { value: "07", label: "07時台" }, 
+  { value: "08", label: "08時台" }, 
+  { value: "09", label: "09時台" },
+  { value: "10", label: "10時台" },
+  { value: "11", label: "11時台" },
+  { value: "12", label: "12時台" },
+  { value: "13", label: "13時台" }
+];
+
+export const LabelGenerationList = () => {
+  const containerStyle = useMemo(() => ({ width: "100%", overflow: "hidden" }), []);
+  const [rowData, setRowData] = useState<any[]>([]);
+  const [, setLoading] = useState<boolean>(true);
+  const [selectedDept, setSelectedDept] = useState("");
+  const [selectedArea, setSelectedArea] = useState("");
+  const [selectedHour, setSelectedHour] = useState("");
+  const [baseCode, setBaseCode] = useState("");
+  const [baseName, setBaseName] = useState("");
+  const [creationDate, setCreationDate] = useState<Dayjs | null>(null);
+  const [isEditMode, setIsEditMode] = useState(false);
+  const [selectedRows, setSelectedRows] = useState<any[]>([]);
+  
+  // React Admin 核心工具
+  const dataProvider = useDataProvider();
+  const params = useParams();
+
+  // 初始化样式
+  useEffect(() => {
+    const style = document.createElement('style');
+    style.textContent = `
+      .ag-theme-alpine .header-center .ag-header-cell-label {
+        justify-content: left !important;
+        text-align: center !important;
+      }
+
+      .ag-theme-alpine .header-center.ag-header-group-cell {
+        text-align: center !important;
+      }
+
+      .ag-theme-alpine .header-center.ag-header-group-cell .ag-header-group-cell-label {
+        justify-content: center !important;
+        display: flex !important;
+        width: 100% !important;
+      }
+
+      .ag-header-cell-resize:after {
+        display: none;
+      }
+      
+     .ag-theme-alpine .ag-cell {
+       border-right: 1px solid #dcdcdc;
+       text-align: center;
+       justify-content: left !important;
+     }
+     
+     .ag-theme-alpine .ag-header-cell {
+       border-right: 1px solid #dcdcdc;
+     }
+     
+     .line-group-header {
+       border-right: 1px solid #dcdcdc;
+     }
+     
+     .date-group-header {
+       border-right: 1px solid #dcdcdc !important;
+     }
+     /* 搜索区域样式 */
+     .search-section {
+       margin-bottom: 10px;
+     }
+     .search-section h3 {
+       font-weight: bold;
+       font-size: 16px;
+     }
+     .search-form {
+       display: flex;
+       flex-direction: column;
+     }
+     .form-row {
+       display: flex;
+       align-items: center;
+       margin-bottom: 5px;
+     }
+     .form-row label {
+       width: 100px;
+       text-align: left;
+       margin-right: 10px;
+     }
+     /* 统一所有下拉列表和输入框样式 */
+     .form-row input,
+     .form-row select,
+     .form-row.ant-picker {
+       padding: 5px;
+       flex: 1;
+       max-width: 200px;
+       border: 1px solid #ccc;
+       border-radius: 4px;
+       box-sizing: border-box;
+     }
+     .top-section {
+       display: flex;
+       align-items: center;
+       margin-bottom: 5px;
+     }
+     .top-section span {
+       margin-right: 10px;
+       font-weight: bold; 
+       font-size: 16px;
+     }
+     .correction-btn{
+       background-color: #000;
+       color: #fff;
+       margin-left: auto;
+       border: none;
+       padding: 5px 20px;
+       cursor: pointer;
+     }
+     .search-btn {
+       background-color: #000;
+       color: #fff;
+       border: none;
+       padding: 5px 40px;
+       cursor: pointer;
+       margin-left: 0;
+     }
+     .cancel-btn {
+       background-color: #000;
+       color: #fff;
+       border: none;
+       padding: 5px 20px;
+       cursor: pointer;
+     }
+     .delete-btn {
+       background-color: #000;
+       color: #fff;
+       border: none;
+       padding: 5px 20px;
+       cursor: pointer;
+     }
+     .loading-indicator {
+       text-align: center;
+       padding: 20px;
+       font-size: 16px;
+     }
+    `;
+    document.head.appendChild(style);
+
+    return () => {
+      document.head.removeChild(style);
+    };
+  }, []);
+
+  // 从路由参数初始化查询条件
+  useEffect(() => {
+    if (params.baseCode) setBaseCode(params.baseCode);
+    if (params.dateTm) setCreationDate(dayjs(params.dateTm));
+  }, [params]);
+
+  // 日期格式化
+  const formatDateForBackend = () => {
+    if (!creationDate) return '';
+    const baseDate = creationDate.format('YYYY-MM-DD');
+    const hour = selectedHour || '00';
+    return `${baseDate} ${hour}:00:00`;
+  };
+
+  // 获取数据
+  const fetchData = async () => {
+    setLoading(true);
+    try {
+      const queryParams = {
+        depName: selectedDept,
+        regName: selectedArea,
+        hour: selectedHour,
+        baseCode,
+        baseName,
+        creationDate: formatDateForBackend() 
+      };
+
+      const response = await dataProvider.getList('label/generations', {
+        pagination: { page: 1, perPage: 100 },
+        sort: { field: 'creationDate', order: 'DESC' },
+        filter: queryParams
+      });
+
+      const formattedData = (response.data || []).map((item: any) => ({
+        checkboxCol: "",
+        id: item.id || "",
+        depName: item.depName || "",
+        regName: item.regName || "",
+        baseCode: item.baseCode || "",
+        baseName: item.baseName || "",
+        zipCode: item.zipCode || "",
+        inNumber: item.inNumber || "",
+        classNumber: item.classNumber || "",
+        creationDate: item.creationDate || "",
+        implementation: item.implementation || ""
+      }));
+      setRowData(formattedData);
+    } catch (error) {
+      console.error("数据获取失败:", error);
+      alert("データの取得に失敗しました。再試行してください。");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 初始加载数据
+  useEffect(() => {
+    fetchData();
+  }, []);
+
+  // 构建列定义
+  const columnDefs: (ColDef | ColGroupDef)[] = useMemo(() => {
+    const baseColumns: ColDef[] = [
+      {
+        field: "depName",
+        headerClass: "header-center", 
+        headerName: "部名",
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ),
+        width: 120,
+        minWidth: 100,
+      },
+      {
+        field: "regName",
+        headerClass: "header-center",
+        headerName: "地区名",
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ),
+        width: 130, 
+        minWidth: 100,
+      },
+      {
+        field: "baseCode",
+        headerClass: "header-center",
+        headerName: "拠点コード",
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ),
+        flex: 1,
+        minWidth: 110,
+      },
+      {
+        field: "baseName",
+        headerClass: "header-center",
+        headerName: "拠点名", 
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ), 
+        flex: 1,
+        minWidth: 170,
+      },
+      {
+        field: "zipCode",
+        headerClass: "header-center",
+        headerName: "郵便番号",
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ),  
+        flex: 1,
+        minWidth: 110,
+      },
+      {
+        field: "inNumber",
+        headerClass: "header-center",
+        headerName: "送り状番号",
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ),  
+        flex: 1.5,
+        minWidth: 130,
+      },
+      {
+        field: "classNumber",
+        headerClass: "header-center",
+        headerName: "仕分け番号(ヤマト運輸)",
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ),  
+        flex: 1.5,
+        minWidth: 130,
+      },
+      {
+        field: "creationDate",
+        headerClass: "header-center",
+        headerName: "生成日時",
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ),
+        flex: 1.5,
+        minWidth: 140,
+      },
+      {
+        field: "implementation",
+        headerClass: "header-center",
+        headerName: "実施者",
+        cellRenderer: (params: ICellRendererParams) => (
+          <div style={{ textAlign: 'left'}}>{params.value || ''}</div>
+        ),
+        flex: 1,
+        minWidth: 100,
+      }
+    ];
+
+    // 编辑模式添加复选框列
+    if (isEditMode) {
+      return [
+        {
+          field: "checkboxCol",
+          headerName: "削除",
+          width: 70,
+          cellRenderer: (params: ICellRendererParams) => {
+            const currentRow = params.data;
+            const isChecked = selectedRows.includes(currentRow);
+            
+            const handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
+              setSelectedRows(prev => 
+                e.target.checked 
+                  ? [...prev, currentRow] 
+                  : prev.filter(row => row!== currentRow)
+              );
+            };
+
+            return (
+              <div style={{ textAlign: 'center' }}>
+                <input 
+                  type="checkbox" 
+                  checked={isChecked} 
+                  onChange={handleCheck} 
+                />
+              </div>
+            );
+          },
+          headerClass: "header-center"
+        },
+        ...baseColumns
+      ];
+    }
+
+    return baseColumns;
+  }, [isEditMode, selectedRows]);
+
+  // 取消编辑
+  const handleCancel = () => {
+    setIsEditMode(false);
+    setSelectedRows([]);
+  };
+
+  // 批量删除
+  const handleBatchDelete = async () => {
+    if (selectedRows.length === 0) {
+      alert("削除するデータを選択してください");
+      return;
+    }
+
+    try {
+      const ids = selectedRows.map(row => row.id);
+      await dataProvider.deleteMany('label/generations', { ids });
+      
+      setRowData(prev => prev.filter(row =>!selectedRows.includes(row)));
+      setSelectedRows([]);
+      alert(`${selectedRows.length}件のデータを削除しました`);
+    } catch (error) {
+      console.error("删除失败:", error);
+      alert("削除に失敗しました。再試行してください。");
+    }
+  };
+
+  return (
+    <div style={containerStyle}>
+      {/* 检索区域 */}
+      <div className="search-section">
+        <h3>ラベル生成ログ検索</h3>
+        <form className="search-form">
+          <div className="form-row">
+            <label>部名</label>
+            <select 
+              value={selectedDept} 
+              onChange={(e) => setSelectedDept(e.target.value)}
+            >
+              {deptOptions.map(option => (
+                <option key={option.value} value={option.value}>
+                  {option.label}
+                </option>
+              ))}
+            </select>
+          </div>
+          
+          <div className="form-row">
+            <label>地区名</label>
+            <select 
+              value={selectedArea} 
+              onChange={(e) => setSelectedArea(e.target.value)}
+            >
+              {areaOptions.map(option => (
+                <option key={option.value} value={option.value}>
+                  {option.label}
+                </option>
+              ))}
+            </select>
+          </div>
+          
+          <div className="form-row">
+            <label>拠点コード</label>
+            <input 
+              type="text" 
+              value={baseCode}
+              onChange={(e) => setBaseCode(e.target.value)}
+            />
+          </div>
+          
+          <div className="form-row">
+            <label>拠点名</label>
+            <input 
+              type="text" 
+              value={baseName}
+              onChange={(e) => setBaseName(e.target.value)}
+            />
+          </div>
+          
+          <div className="form-row">
+            <label>生成日</label>
+            <DatePicker
+              value={creationDate}
+              onChange={(date) => setCreationDate(date)}
+              format="YYYY-MM-DD"
+              style={{ maxWidth: '200px', flex: 1 }}
+            />
+            <div style={{ marginLeft: '30px' }}>
+              <select 
+                value={selectedHour} 
+                onChange={(e) => setSelectedHour(e.target.value)}
+              >
+                {hourOptions.map(option => (
+                  <option key={option.value} value={option.value}>
+                    {option.label}
+                  </option>
+                ))}
+              </select>
+              <span>時台</span>
+            </div>
+          </div>
+          
+          <div className="form-row">
+            <button 
+              type="button" 
+              className="search-btn"
+              onClick={fetchData}
+            >
+              検索
+            </button>
+          </div>
+        </form>
+      </div>
+      
+      {/* 表格标题区域 */}
+      <div className="top-section">
+        <span>ラベル生成ログ</span>
+        <button 
+          className="correction-btn" 
+          onClick={() => setIsEditMode(true)}
+          style={{ display: isEditMode? 'none' : 'block', marginLeft: 'auto' }}
+        >
+          実績値修正
+        </button>
+      </div>
+      
+      {/* 数据表格*/}
+      <div className="ag-theme-alpine" style={{ width: '100%', minWidth: '900px' }}>
+        <AgGridReact
+          rowData={rowData}
+          columnDefs={columnDefs}
+          domLayout="autoHeight"
+          suppressHorizontalScroll={false}
+          rowHeight={35}
+          headerHeight={35}
+        />
+      </div>
+      
+      {/* 编辑模式操作按钮*/}
+      {isEditMode && (
+        <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '10px' }}>
+          <button 
+            className="cancel-btn" 
+            style={{ marginRight: '40px' }}
+            onClick={handleCancel}
+          >
+            キャンセル
+          </button>
+          <button 
+            className="delete-btn"
+            onClick={handleBatchDelete}
+          >
+            一括削除
+          </button>
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default LabelGenerationList;

ファイルの差分が大きいため隠しています
+ 637 - 4
new-react-admin-ui/yarn.lock