cqp hace 3 meses
padre
commit
f5606df128

+ 22 - 0
app/Http/Controllers/Api/ExportFileController.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Service\ExportFileService;
+use Illuminate\Http\Request;
+
+class ExportFileController extends BaseController
+{
+    public function exportFile(Request $request)
+    {
+        $service = new ExportFileService();
+        $userData = $request->userData;
+        list($status,$data) = $service->exportAll($request->all(),$userData);
+
+        if($status){
+            return $this->json_return(200,'',['file'=>$data]);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+}

+ 39 - 0
app/Http/Controllers/Api/ImportController.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Service\ImportService;
+use Illuminate\Http\Request;
+
+class ImportController extends BaseController
+{
+    public function getTableTitleXls(Request $request)
+    {
+        $service = new ImportService();
+        $userData = $request->userData;
+        list($status,$data) = $service->getTableTitleXls($request->all(),$userData);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            return $this->json_return(201,$data);
+        }
+    }
+
+    public function importAll(Request $request)
+    {
+        $service = new ImportService();
+        $userData = $request->userData;
+        list($status,$data) = $service->importAll($request->all(),$userData);
+
+        if($status){
+            return $this->json_return(200,'',$data);
+        }else{
+            if($status === 0){
+                return $this->json_return(199,$data);
+            }else{
+                return $this->json_return(201,$data);
+            }
+        }
+    }
+}

+ 1 - 0
app/Service/DeviceService.php

@@ -172,6 +172,7 @@ class DeviceService extends Service
         $emp = (new EmployeeService())->getEmployeeMap(array_unique(array_column($data['data'],'crt_id')));
         foreach ($data['data'] as $key => $value){
             $data['data'][$key]['crt_time'] = $value['crt_time'] ? date('Y-m-d H:i:s',$value['crt_time']) : '';
+            $data['data'][$key]['in_time'] = $value['in_time'] ? date('Y-m-d',$value['in_time']) : '';
             $data['data'][$key]['crt_name'] = $emp[$value['crt_id']] ?? '';
             $data['data'][$key]['is_use_title'] = Device::Use[$value['is_use']] ?? '';
             $data['data'][$key]['type_title'] = Device::$type[$value['type']] ?? '';

+ 19 - 0
app/Service/EmployeeService.php

@@ -10,6 +10,7 @@ use App\Model\EmployeeRole;
 use App\Model\Role;
 use App\Model\RoleMenu;
 use App\Model\RoleMenuButton;
+use App\Model\SysMenu;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Hash;
 use Mockery\Exception;
@@ -677,4 +678,22 @@ class EmployeeService extends Service
             ->pluck('title', 'id')
             ->toArray();
     }
+
+    public static function fillMenu2($menu_id, &$user){
+        // 直接查询匹配的菜单
+        $menuItem = SysMenu::where('del_time',0)
+            ->where('id', $menu_id)
+            ->first();
+
+        $func = $menuItem ? $menuItem->export_file_func : "";
+        $funcName = $menuItem ? $menuItem->title : "";
+
+        $header_default = config("excel." . $func) ?? [];
+//        $header_f = "extra_" . $menu_id;
+//        $service = new TableHeadService();
+//        if(method_exists($service,$header_f)) $service->$header_f($header_default);
+        $user['e_header_default'] = $header_default;
+
+        return [$func, $funcName];
+    }
 }

+ 229 - 0
app/Service/ExportFileService.php

@@ -0,0 +1,229 @@
+<?php
+
+namespace App\Service;
+
+use App\Exports\ExportOrder;
+use App\Exports\MultiSheetExport;
+use App\Model\RevenueCost;
+use Maatwebsite\Excel\Facades\Excel;
+
+class ExportFileService extends Service
+{
+    public static $filename = "";
+
+    //导出的方式 0 选择导出的数据 1 查询后 导出指定的数据(最多每次一千条)
+    public static $export_type = [
+        0,
+        1
+    ];
+
+    public function exportAll($data,$user){
+        if(empty($data['menu_id'])) return [false, '菜单ID不能为空'];
+        list($function, $name) = EmployeeService::fillMenu2($data['menu_id'], $user);
+        if (empty($function) || ! method_exists(self::class, $function)) return [false, "导出方法不存在,请联系开发"];
+        self::$filename = $name;
+
+        $export_type = $data['export_type'] ?? 0;
+        if(! isset(self::$export_type[$export_type])) return [false,'导出文件方式错误或者不存在'];
+        if(empty($export_type)){
+            if(empty($data['id'])) return [false,'请选择导出数据'];
+            $search = $data;
+        }else{
+            if(empty($data['order_search'])) return [false,'搜索条件不能为空'];
+            $search = $data['order_search'];
+            if(empty($search['page_index'])) return [false,'请选择导出数据的开始页码'];
+            if(empty($search['page_size'])) return [false,'请选择导出数据的条数'];
+            if($search['page_size'] > 5000) return [false,'请选择导出数据的条数每次最多5000条'];
+            $data['order_search']['menu_id'] = $data['menu_id'];
+            $search = $data['order_search'];
+        }
+
+        list($status, $return) = $this->$function($search,$user);
+        if(! $status) return [false, $return];
+
+        return [true, $return];
+    }
+
+    private function fillData($data, $column, &$return)
+    {
+        // 预先创建包含默认值的键数组
+        $default = array_fill_keys($column, '');
+
+        foreach ($data as $value) {
+            // 提取交集,并用默认值补充缺失的键
+            $return[] = array_merge($default, array_intersect_key($value, $default));
+        }
+    }
+
+    private function fillTotalData($data, $column, &$return){
+        $tmp = [];
+        foreach ($column as $value){
+            $key = $value['key'];
+            if(! empty($value['sum']) && isset($data[$value['key']])){
+                $decimals = $col['decimals'] ?? 2;
+//                $tmp[$value['key']] = $data[$value['key']];
+
+                // 用 number_format 格式化输出(保持字符串形式,避免科学计数)
+                $tmp[$key] = number_format((float)$data[$key], $decimals, '.', '');
+            }else{
+                $tmp[$value['key']] = "";
+            }
+        }
+
+        $return[] = $tmp;
+    }
+
+    public function device($ergs,$user){
+        // 导出数据
+        $return = [];
+        $header_default = $user['e_header_default'];
+        $column = array_column($header_default,'export');
+        $header = array_column($header_default,'value');
+
+        $service = new DeviceService();
+        $model = $service->deviceCommon($ergs, $user);
+
+        $model->chunk(500,function ($data) use(&$return, $service, $column){
+            $data = $data->toArray();
+            $list['data'] = $data;
+
+            //订单数据
+            $list = $service->fillData($list);
+            //返回数据
+            $this->fillData($list['data'], $column, $return);
+        });
+
+        return [true, $this->saveExportData($return,$header)];
+    }
+
+    public function item($ergs,$user){
+        // 导出数据
+        $return = [];
+
+        $header_default = $user['e_header_default'];
+        $column = array_column($header_default,'export');
+        $header = array_column($header_default,'value');
+
+        $service = new ItemService();
+        $model = $service->itemCommon($ergs, $user);
+        $model->chunk(500,function ($data) use(&$return,$column,$service){
+            $data = $data->toArray();
+
+            $list['data'] = $data;
+            $list = $service->fillData($list, true);
+
+            $this->fillData($list['data'], $column, $return);
+        });
+
+        return [true, $this->saveExportData($return,$header)];
+    }
+
+    public function one($ergs, $user){
+        $service = new EmployeeService();
+        $model = $service->employeeCommon($ergs, $user);
+
+        // 导出数据
+        $return = [];
+
+        $header_default = $user['e_header_default'];
+        $column = array_column($header_default,'key');
+        $header = array_column($header_default,'value');
+        $model->chunk(500,function ($data) use(&$return,$column,$service){
+            $data = $data->toArray();
+            $list['data'] = $data;
+            $list = $service->organizationEmployeeData($list);
+
+            $this->fillData($list['data'], $column, $return);
+        });
+
+        return [true, $this->saveExportData($return,$header)];
+    }
+
+    public function two($ergs,$user){
+        // 导出数据
+        $return = [];
+
+        $header_default = $user['e_header_default'];
+        $column = array_column($header_default,'key');
+        $header = array_column($header_default,'value');
+
+        $service = new ItemService();
+        $model = $service->itemCommon($ergs, $user);
+        $model->chunk(500,function ($data) use(&$return,$column,$service){
+            $data = $data->toArray();
+
+            $list['data'] = $data;
+            $list = $service->fillData($list);
+
+            $this->fillData($list['data'], $column, $return);
+        });
+
+        return [true, $this->saveExportData($return,$header)];
+    }
+
+    public function three($ergs,$user){
+        // 导出数据
+        $return = [];
+        $header_default = $user['e_header_default'];
+        $column = array_column($header_default,'key');
+        $header = array_column($header_default,'value');
+
+        $service = new DeviceService();
+        $model = $service->deviceCommon($ergs, $user);
+
+        $model->chunk(500,function ($data) use(&$return, $service, $column){
+            $data = $data->toArray();
+            $list['data'] = $data;
+
+            //订单数据
+            $list = $service->fillData($list);
+            //返回数据
+            $this->fillData($list['data'], $column, $return);
+        });
+
+        return [true, $this->saveExportData($return,$header)];
+    }
+
+    public function four($ergs,$user){
+        // 导出数据
+        $return = [];
+        $header_default = $user['e_header_default'];
+        $column = array_column($header_default,'key');
+        $header = array_column($header_default,'value');
+
+        $service = new RDService();
+        $model = $service->rdCommon($ergs, $user);
+
+        $model->chunk(500,function ($data) use(&$return, $column, $service){
+            $data = $data->toArray();
+            $list['data'] = $data;
+
+            //订单数据
+            $list = $service->fillData($list);
+
+            $this->fillData($list['data'], $column, $return);
+        });
+
+        //合计
+        $total = $this->countTotal($return, $header_default);
+        //填充合计
+        $this->fillTotalData($total, $header_default, $return);
+
+        return [true, $this->saveExportData($return,$header)];
+    }
+
+    public function saveExportData($data, $headers, $type = 'default',$file_name = ''){
+        if(empty($file_name)) $file_name = self::$filename . "_". date("Y-m-d") . "_". rand(1000,9999);
+        $filename =  $file_name . '.' . 'xlsx';
+        $bool = Excel::store(new ExportOrder($data,$type,$headers),"/public/export/{$filename}", null, 'Xlsx', []);
+        return $filename;
+    }
+
+    public function saveExportData2($data,$type = 1,$column,$timeRow, $file_name = ''){
+        if(empty($file_name)) $file_name = self::$filename . "_". date("Y-m-d") . "_". rand(1000,9999);
+        $filename =  $file_name . '.' . 'xlsx';
+        \Maatwebsite\Excel\Facades\Excel::store(new MultiSheetExport($data, $type,$column,$timeRow),"/public/export/{$filename}", null, 'Xlsx', []);
+
+        return $filename;
+    }
+}

+ 596 - 0
app/Service/ImportService.php

@@ -0,0 +1,596 @@
+<?php
+
+namespace App\Service;
+
+use App\Exports\TableHeadExport;
+use App\Import\ImportAll;
+use App\Model\Device;
+use App\Model\Employee;
+use App\Model\Item;
+use App\Model\ItemDetails;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Maatwebsite\Excel\Facades\Excel;
+use PhpOffice\PhpSpreadsheet\IOFactory;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+
+class ImportService extends Service
+{
+    public static $type = [
+        'device', // 设备
+        'item', // 项目
+    ];
+
+    public function getTableTitleXls($data,$user){
+        if(empty($data['type'])) return [false,'缺少类型'];
+        if(! in_array($data['type'],self::$type)) return [false,'类型不存在'];
+
+        //获取配置文件
+        $fuc = $data['type'];
+        list($status,$return) = $this->$fuc($data,$user);
+        list($msg,$filename) = $return;
+        if(! $status) return [false, $msg];
+
+        $headers = array_column($msg,'value');
+        $comments = $enums = [];
+        foreach ($msg as $value){
+            if(! empty($value['comments'])) $comments[$value['value']] = $value['comments'];
+            if(! empty($value['enums'])) $enums[$value['value']] = $value['enums'];
+        }
+        Excel::store(new TableHeadExport([], $headers, $comments, $enums),"/public/export/{$filename}", null, 'Xlsx', []);
+        return [true, ['file' => $filename]];
+    }
+
+    private function getTableConfig($type = ""){
+        if(empty($type)) return [];
+
+        //获取配置文件
+        $config = "excel." . $type;
+        return config($config) ?? [];
+    }
+
+    private function device($data,$user){
+        $config_array = $this->getTableConfig($data['type']);
+        if(empty($config_array)) return [false, ['导入配置表头文件不存在','']];
+
+        //生成下载文件
+        $filename =  "设备导入模板_" . time() . '.' . 'xlsx';
+
+        return [true, [$config_array, $filename]];
+    }
+
+    private function item($data,$user){
+        $config_array = $this->getTableConfig($data['type']);
+        if(empty($config_array)) return [false, ['导入配置表头文件不存在','']];
+
+        //生成下载文件
+        $filename =  "项目导入模板_" . time() . '.' . 'xlsx';
+
+        return [true, [$config_array, $filename]];
+    }
+
+    //导入入口
+    public function importAll($data,$user){
+//        //不超时
+//        ini_set('max_execution_time', 0);
+//        //内存设置
+//        ini_set('memory_limit', -1);
+//        $reader = IOFactory::createReader('Xlsx');
+//        $reader->setReadDataOnly(true); // 只读取有数据的单元格
+//        $spreadsheet = $reader->load($data['file']);
+//        dd($spreadsheet);
+//        // 创建一个Reader对象
+//        $reader = IOFactory::createReader('Xlsx'); // 根据你的文件格式选择合适的reader
+//
+//// 加载Excel文件
+//        $spreadsheet = $reader->load($data['file']);
+//
+//// 获取第一个工作表
+//        $worksheet = $spreadsheet->getActiveSheet();
+//
+//// 获取总行数
+//        $totalRows = $worksheet->getHighestRow();dd($totalRows);
+        if(empty($data['type'])) return [false,'缺少导入类型,导入失败'];
+        if(! in_array($data['type'],self::$type)) return [false,'导入类型不存在,导入失败'];
+        if(empty($data['file'])) return [false,'导入文件不能为空'];
+
+        try {
+            $import = new ImportAll();
+            //设置导入人id
+            $import->setCrt($user['id']);
+            $import->setUser($user);
+            $import->setType($data['type']);
+            $other = $data;
+            unset($other['file']);
+            $import->setOtherParam($other);
+
+            //导入
+            \Maatwebsite\Excel\Facades\Excel::import($import,$data['file']);
+            if($import->getMsg()) {
+                $bool = $import->getIsLongText();
+                if($bool) {
+                    return [0, $import->getMsg()];
+                }else{
+                    return [false, $import->getMsg()];
+                }
+            }
+        }catch (\Throwable $exception) {
+            return [false, $exception->getMessage() . ' (Code: ' . $exception->getCode() . ', Line: ' . $exception->getLine() . ')'];
+        }
+
+        return [true, ''];
+    }
+
+    // 设备 ------------------------------------
+    public function deviceImport($array, $user, $other_param){
+        $upload = $array[0];
+        list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
+        if(!$status) return [false, $msg];
+        $table_config = $msg;
+
+        unset($array[0]);
+        if(empty($array)) return [false, '导入数据不能为空'];
+
+        // 公共校验
+        list($array, $error) = $this->checkCommon($array, $table_config);
+        if(!empty($error)) return [0, $error];
+
+        // 详细校验 (这里 $array 传引用,内部会转换日期)
+        list($error, $update_map) = $this->deviceCheck($array, $user, $table_config);
+        if(!empty($error)) return [0, $error];
+
+        $time = time();
+        $insert = [];
+        $update = [];
+
+        foreach ($array as $key => $value){
+            $main_tmp = [];
+            foreach ($value as $k => $val){
+                if(!empty($table_config[$k]['is_main'])){
+                    $main_tmp[$table_config[$k]['key']] = $val;
+                }
+            }
+
+            if(isset($update_map[$key])){
+                // 存入待更新数组
+                $update[] = array_merge($main_tmp, ['id' => $update_map[$key]]);
+            } else {
+                $main_tmp['top_depart_id'] = $user['top_depart_id'];
+                $main_tmp['crt_id'] = $user['id'];
+                $main_tmp['crt_time'] = $time;
+                $insert[] = $main_tmp;
+            }
+        }
+
+        DB::beginTransaction();
+        try {
+            // 1. 批量新增
+            if(!empty($insert)){
+                foreach(array_chunk($insert, 500) as $chunkInsert){
+                    Device::insert($chunkInsert);
+                }
+            }
+
+            // 批量更新
+            foreach (array_chunk($update, 100) as $chunk) {
+                foreach ($chunk as $item) {
+                    $id = $item['id'];
+                    unset($item['id']);
+                    Device::where('id', $id)->update($item);
+                }
+            }
+
+            DB::commit();
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return [false, "错误:" . $e->getMessage() . " 行:" . $e->getLine()];
+        }
+
+        return [true, ''];
+    }
+
+    private function deviceCheck(&$array, $user, $table_config)
+    {
+        // 动态获取关键列的索引
+        $codeIdx = array_search('code', array_column($table_config, 'key'));
+        $dateIdx = array_search('in_time', array_column($table_config, 'key'));
+        $typeIdx = array_search('type', array_column($table_config, 'key'));
+        $type2Idx = array_search('is_use', array_column($table_config, 'key'));
+
+        $code_map = $this->getDeviceList($array, $user, $codeIdx);
+
+        $errors = [];
+        $update = [];
+
+        $map_type = array_flip(Device::$type);
+        $map_type_2 = array_flip(Device::Use);
+        foreach ($array as $rowIndex => $value) {
+            $valCode = $value[$codeIdx] ?? '';
+
+            // 记录更新 ID
+            if(isset($code_map[$valCode])){
+                $update[$rowIndex] = $code_map[$valCode];
+            }
+
+            if(empty($map_type[$value[$typeIdx]])){
+                $errors[] = "第{$rowIndex}行固定资产类型错误";
+            }else{
+                $array[$rowIndex][$typeIdx] = $map_type[$value[$typeIdx]];
+            }
+            if(empty($map_type_2[$value[$type2Idx]])){
+                $errors[] = "第{$rowIndex}行是否启用错误";
+            }else{
+                $array[$rowIndex][$type2Idx] = $map_type_2[$value[$type2Idx]];
+            }
+
+            // 日期处理
+            if($dateIdx !== false && !empty($value[$dateIdx])){
+                list($status, $msg) = $this->convertExcelCellToDate($value[$dateIdx]);
+                if(!$status) {
+                    $errors[] = "第{$rowIndex}行日期格式错误";
+                } else {
+                    $array[$rowIndex][$dateIdx] = $msg;
+                }
+            }
+        }
+
+        $error_string = "";
+        if(! empty($errors)) $error_string = implode('|', $errors);
+
+        return [$error_string, $update];
+    }
+
+    private function getDeviceList($array, $user, $index){
+        //查找设备
+        $codes = array_unique(array_filter(array_column($array,$index)));
+
+        return Device::where('del_time', 0)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('code', $codes)
+            ->pluck('id','code')
+            ->toArray();
+    }
+
+    // 项目 -----------------------------------
+    public function itemImport($array, $user, $other_param)
+    {
+        $upload = $array[0];
+        list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
+        if (!$status) return [false, $msg];
+        $table_config = $msg;
+
+        unset($array[0]);
+        if (empty($array)) return [false, '导入数据不能为空'];
+
+        // 1. 公共校验 (必填、唯一性等)
+        list($array, $error) = $this->checkCommon($array, $table_config);
+        if (!empty($error)) return [0, $error];
+
+        // 2. 业务详细校验 (获取更新映射及明细数据)
+        list($error, $update_map, $detail_data_map) = $this->itemCheck($array, $user, $table_config);
+        if (!empty($error)) return [0, $error];
+
+        $time = time();
+        $insert_data = [];
+        $update_data = [];
+        $all_detail_insert = [];
+        $update_main_ids = [];
+
+        // 3. 数据分拣
+        foreach ($array as $key => $value) {
+            $main_tmp = [];
+            foreach ($value as $k => $val) {
+                if (!empty($table_config[$k]['is_main'])) {
+                    $main_tmp[$table_config[$k]['key']] = $val;
+                }
+            }
+
+            if (isset($update_map[$key])) {
+                // 更新逻辑
+                $itemId = $update_map[$key];
+                $update_data[] = array_merge($main_tmp, ['id' => $itemId]);
+                $update_main_ids[] = $itemId;
+
+                // 收集明细 (后续统一插入)
+                if (isset($detail_data_map[$key])) {
+                    foreach ($detail_data_map[$key] as $d) {
+                        $all_detail_insert[] = array_merge($d, ['item_id' => $itemId, 'crt_time' => $time]);
+                    }
+                }
+            } else {
+                // 新增逻辑
+                $main_tmp['top_depart_id'] = $user['top_depart_id'];
+                $main_tmp['crt_id'] = $user['id'];
+                $main_tmp['crt_time'] = $time;
+                // 以 code 为键,方便后续回填 ID
+                $insert_data[$main_tmp['code']] = $main_tmp;
+
+                if (isset($detail_data_map[$key])) {
+                    foreach ($detail_data_map[$key] as $d) {
+                        $all_detail_insert[] = array_merge($d, ['_code' => $main_tmp['code'], 'crt_time' => $time]);
+                    }
+                }
+            }
+        }
+
+        DB::beginTransaction();
+        try {
+            // 4. 执行新增主表
+            if (!empty($insert_data)) {
+                foreach (array_chunk($insert_data, 500) as $chunk) {
+                    Item::insert($chunk);
+                }
+                // 获取新插入数据的 ID 映射
+                $new_item_maps = Item::whereIn('code', array_keys($insert_data))
+                    ->where('del_time', 0)
+                    ->where('top_depart_id', $user['top_depart_id'])
+                    ->pluck('id', 'code')->toArray();
+            }
+
+            // 5. 执行更新主表 (分批更新)
+            if (!empty($update_data)) {
+                foreach (array_chunk($update_data, 100) as $chunk) {
+                    foreach ($chunk as $uItem) {
+                        $id = $uItem['id'];
+                        unset($uItem['id']);
+                        Item::where('id', $id)->update($uItem);
+                    }
+                }
+            }
+
+            // 6. 处理明细表 (先删后插策略)
+            // 删除旧明细 (逻辑删除)
+            if (!empty($update_main_ids)) {
+                ItemDetails::whereIn('item_id', $update_main_ids)
+                    ->where('del_time', 0)
+                    ->update(['del_time' => $time]);
+            }
+
+            // 回填新增主表的 ID 到明细数组
+            foreach ($all_detail_insert as &$di) {
+                if (isset($di['_code'])) {
+                    $di['item_id'] = $new_item_maps[$di['_code']] ?? 0;
+                    unset($di['_code']);
+                }
+            }
+            unset($di);
+
+            // 批量插入所有明细
+            if (!empty($all_detail_insert)) {
+                foreach (array_chunk($all_detail_insert, 500) as $chunk) {
+                    ItemDetails::insert($chunk);
+                }
+            }
+
+            DB::commit();
+        } catch (\Exception $e) {
+            DB::rollBack();
+            return [false, "入库失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
+        }
+
+        return [true, '导入成功'];
+    }
+
+    private function getItemList($array, $user, $index){
+        //查找设备
+        $codes = array_unique(array_filter(array_column($array,$index)));
+
+        return Item::where('del_time', 0)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('code', $codes)
+            ->pluck('id','code')
+            ->toArray();
+    }
+
+    private function itemCheck(&$array, $user, $table_config)
+    {
+        $keys = array_column($table_config, 'key');
+        $codeIdx = array_search('code', $keys);
+        $stateIdx = array_search('state', $keys);
+        $manIdx = array_search('man_list', $keys);
+        $deviceIdx = array_search('device_list', $keys);
+        // 日期索引
+        $dateIdx = array_search('start_time', $keys);
+        $date2Idx = array_search('end_time', $keys);
+
+        $code_map = $this->getItemList($array, $user, $codeIdx);
+        list($man_map, $device_map) = $this->getDataList($array, $user, $manIdx, $deviceIdx);
+
+        $errors = [];
+        $update_mapping = [];
+        $detail_storage = [];
+        $state_type_map = array_flip(Item::State_Type);
+
+        foreach ($array as $rowIndex => $rowValue) {
+            $valCode = $rowValue[$codeIdx] ?? '';
+
+            // 1. 判定更新还是新增
+            if (isset($code_map[$valCode])) {
+                $update_mapping[$rowIndex] = $code_map[$valCode];
+            }
+
+            // 2. 状态校验
+            $state_text = $rowValue[$stateIdx] ?? '';
+            if (!isset($state_type_map[$state_text])) {
+                $errors[] = "第{$rowIndex}行:状态[{$state_text}]无效";
+            } else {
+                $array[$rowIndex][$stateIdx] = $state_type_map[$state_text];
+            }
+
+            // 3. 日期转换
+            foreach ([$dateIdx, $date2Idx] as $dIdx) {
+                if ($dIdx !== false && !empty($rowValue[$dIdx])) {
+                    list($s, $m) = $this->convertExcelCellToDate($rowValue[$dIdx]);
+                    if (!$s) $errors[] = "第{$rowIndex}行:日期格式非法";
+                    else $array[$rowIndex][$dIdx] = $m;
+                }
+            }
+
+            // 4. 解析人员 (明细类型1)
+            if ($manIdx !== false && !empty($rowValue[$manIdx])) {
+                foreach (explode(',', $rowValue[$manIdx]) as $mNum) {
+                    $mNum = trim($mNum);
+                    if (!isset($man_map[$mNum])) {
+                        $errors[] = "第{$rowIndex}行:人员编码[{$mNum}]不存在";
+                    } else {
+                        $detail_storage[$rowIndex][] = [
+                            'type' => ItemDetails::type_one,
+                            'data_id' => $man_map[$mNum],
+                        ];
+                    }
+                }
+            }
+
+            // 5. 解析设备 (明细类型2)
+            if ($deviceIdx !== false && !empty($rowValue[$deviceIdx])) {
+                foreach (explode(',', $rowValue[$deviceIdx]) as $dCode) {
+                    $dCode = trim($dCode);
+                    if (!isset($device_map[$dCode])) {
+                        $errors[] = "第{$rowIndex}行:资产编码[{$dCode}]不存在";
+                    } else {
+                        $detail_storage[$rowIndex][] = [
+                            'type' => ItemDetails::type_two,
+                            'data_id' => $device_map[$dCode],
+                        ];
+                    }
+                }
+            }
+        }
+
+        $error_str = !empty($errors) ? implode('|', $errors) : "";
+        return [$error_str, $update_mapping, $detail_storage];
+    }
+
+    private function getDataList($array, $user, $index1, $index2)
+    {
+        $manNums = [];
+        $devCodes = [];
+
+        // 去重收集
+        foreach ($array as $row) {
+            if (!empty($row[$index1])) {
+                foreach (explode(',', $row[$index1]) as $v) $manNums[trim($v)] = true;
+            }
+            if (!empty($row[$index2])) {
+                foreach (explode(',', $row[$index2]) as $v) $devCodes[trim($v)] = true;
+            }
+        }
+
+        $manMap = Employee::where('del_time', 0)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('number', array_keys($manNums))
+            ->pluck('id', 'number')->toArray();
+
+        $devMap = Device::where('del_time', 0)
+            ->where('top_depart_id', $user['top_depart_id'])
+            ->whereIn('code', array_keys($devCodes))
+            ->pluck('id', 'code')->toArray();
+
+        return [$manMap, $devMap];
+    }
+
+
+
+    //公共校验 -----------------------------------------
+    private function checkCommon($array, $table_config) {
+        $error = [];
+        $uniqueCheck = []; // 格式:[$column_index => [$value => $first_line]]
+
+        foreach ($array as $line => $row) {
+            $rowData = array_filter($row);
+            if (empty($rowData)) {
+                unset($array[$line]);
+                continue;
+            }
+
+            foreach ($row as $colIndex => $value) {
+                $value = trim($value);
+                $config = $table_config[$colIndex] ?? null;
+
+                // 1. 基础存在性检查
+                if (!$config) {
+                    $error[] = "第{$line}行第{$colIndex}列配置不存在";
+                    continue;
+                }
+
+                $fieldName = $config['value'];
+
+                // 2. 必填校验
+                if (!empty($config['required']) && ($value === '' || !isset($value))) {
+                    $error[] = "第{$line}行:[{$fieldName}] 必填";
+                }
+
+                // 3. 默认值填充
+                if ($value === '' && isset($config['default'])) {
+                    $value = $config['default'];
+                }
+
+                // 4. 唯一性校验(重点:一次遍历解决)
+                if (!empty($config['unique']) && $value !== '') {
+                    if (isset($uniqueCheck[$colIndex][$value])) {
+                        $prevLine = $uniqueCheck[$colIndex][$value];
+                        $error[] = "第{$line}行:[{$fieldName}] 与第{$prevLine}行重复,重复值:{$value}";
+                    } else {
+                        // 记录该值第一次出现的位置
+                        $uniqueCheck[$colIndex][$value] = $line;
+                    }
+                }
+
+                $row[$colIndex] = $value;
+            }
+            $array[$line] = $row;
+        }
+
+        $error_string = !empty($error) ? implode('|', $error) : "";
+        return [$array, $error_string];
+    }
+
+    //模板校验 -----------------------------------------
+    private function compareTableAndReturn($upload, $param){
+        if(empty($upload)) return [false, '表头不能为空'];
+        $config_array = $this->getTableConfig($param['type']);
+        if(empty($config_array)) return [false, '导入配置表头文件不存在'];
+
+        foreach ($config_array as $key => $value){
+            $key_position = $key + 1;
+            if(! isset($upload[$key])) return [false, "第" . $key_position . "列表头缺失"];
+            $tmp_v = trim($upload[$key]);
+            if($tmp_v != $value['value']) return [false, "第" . $key_position . "列表头与模板不符合,请重新下载模板"];
+        }
+
+        return [true, $config_array];
+    }
+
+    //转换日期 ------------------------------------------
+    function convertExcelCellToDate($cellValue) {
+        // 尝试将单元格值转换为浮点数(Excel 日期序列号)
+        $excelTimestamp = filter_var($cellValue, FILTER_VALIDATE_FLOAT);
+
+        if ($excelTimestamp !== false && $excelTimestamp > 0) {
+            // 如果成功转换并且值大于0,则认为是Excel日期序列号
+            try {
+                $dateTimeObject = Date::excelToDateTimeObject($cellValue);
+
+//                if ($dateTimeObject->format('H:i:s') === '00:00:00') {
+//                    // 如果是,则将时间设置为 '23:59:00'
+//                    $dateTimeObject->setTime(23, 59);
+//                }
+
+                // 现在你可以格式化这个日期了
+                $formattedDate = $dateTimeObject->format('Y-m-d');
+                if(! strtotime($formattedDate)) return [false, ''];
+
+                return [true,  strtotime($formattedDate)];
+            } catch (\Exception $e) {
+                // 处理转换失败的情况
+                return [false, '单元格日期格式转换时间戳失败'];
+            }
+        }
+
+        // 如果不是有效的浮点数,则尝试按照多种日期格式解析
+        if(! strtotime($cellValue)) return [false, '单元格文本格式转换时间戳失败'];
+
+        return [true, strtotime($cellValue)];
+    }
+}
+

+ 58 - 1
app/Service/ItemService.php

@@ -275,18 +275,75 @@ class ItemService extends Service
         return [true, ''];
     }
 
-    public function fillData($data){
+    public function fillData($data, $is_export = false){
         if(empty($data['data'])) return $data;
 
         $emp = (new EmployeeService())->getEmployeeMap(array_unique(array_column($data['data'],'crt_id')));
+        $map = $this->getDetailsMap(array_column($data['data'],'id'));
         foreach ($data['data'] as $key => $value){
             $data['data'][$key]['crt_time'] = $value['crt_time'] ? date('Y-m-d H:i:s',$value['crt_time']) : '';
             $data['data'][$key]['start_time'] = $value['start_time'] ? date('Y-m-d',$value['start_time']) : '';
             $data['data'][$key]['end_time'] = $value['end_time'] ? date('Y-m-d',$value['end_time']) : '';
             $data['data'][$key]['crt_name'] = $emp[$value['crt_id']] ?? '';
             $data['data'][$key]['state_title'] = Item::State_Type[$value['state']] ?? "";
+            $tmp = $map[$value['id']];
+            $data['data'][$key]['man_list'] = $tmp['man'];
+            $data['data'][$key]['device_list'] = $tmp['device'];
         }
 
         return $data;
     }
+
+    public function getDetailsMap($item_id){
+        // 1. 获取明细数据,注意一定要选出 item_id
+        $data = ItemDetails::where('del_time', 0)
+            ->whereIn('item_id', $item_id)
+            ->select('item_id', 'data_id', 'type')
+            ->get();
+
+        // 2. 收集 ID 并按类型分类(去重以减少查询压力)
+        $manIds = $data->where('type', ItemDetails::type_one)->pluck('data_id')->unique()->toArray();
+        $devIds = $data->where('type', ItemDetails::type_two)->pluck('data_id')->unique()->toArray();
+
+        // 3. 一次性查出对应的映射 (假设人员表字段是 number, 设备表是 code)
+        // 注意:pluck('显示内容', 'ID')
+        $manMap = Employee::whereIn('id', $manIds)->pluck('number', 'id')->toArray();
+        $devMap = Device::whereIn('id', $devIds)->pluck('code', 'id')->toArray();
+
+        // 4. 按 item_id 分组处理成字符串
+        $result = [];
+        foreach ($item_id as $id) {
+            // 初始化每个 item_id 的默认结构
+            $result[$id] = [
+                'man' => '',
+                'device' => ''
+            ];
+        }
+
+        // 5. 遍历明细填充数据
+        $grouped = $data->groupBy('item_id');
+        foreach ($grouped as $itemId => $items) {
+            $mans = [];
+            $devices = [];
+
+            foreach ($items as $item) {
+                if ($item->type == ItemDetails::type_one) {
+                    if (isset($manMap[$item->data_id])) {
+                        $mans[] = $manMap[$item->data_id];
+                    }
+                } else {
+                    if (isset($devMap[$item->data_id])) {
+                        $devices[] = $devMap[$item->data_id];
+                    }
+                }
+            }
+
+            $result[$itemId] = [
+                'man' => implode(',', $mans),
+                'device' => implode(',', $devices)
+            ];
+        }
+
+        return $result;
+    }
 }

+ 92 - 0
config/excel/device.php

@@ -0,0 +1,92 @@
+<?php
+return [
+    [
+        'key' =>'code',
+        'export' =>'code',
+        'value' => '固定资产编码',
+        'required' => true,
+        'is_main' => true,
+        'default' => "",
+        'unique' => true,
+        'enums' => [],
+        'comments' => '必填'
+    ],
+    [
+        'key' =>'title',
+        'export' =>'title',
+        'value' => '设备名称',
+        'required' => true,
+        'is_main' => true,
+        'default' => "",
+        'unique' => false,
+        'enums' => [],
+        'comments' => '必填'
+    ],
+    [
+        'key' =>'type',
+        'export' =>'type_title',
+        'value' => '固定资产类别',
+        'required' => true,
+        'is_main' => true,
+        'default' => 0,
+        'unique' => false,
+        'enums' => ['电子设备', '机器设备', '办公设备'],
+        'comments' => '必填'
+    ],
+    [
+        'key' =>'in_time',
+        'export' =>'in_time',
+        'value' => '入账日期',
+        'required' => false,
+        'is_main' => true,
+        'unique' => false,
+        'default' => "",
+    ],
+    [
+        'key' =>'size',
+        'export' =>'size',
+        'value' => '规格型号',
+        'required' => false,
+        'is_main' => true,
+        'unique' => false,
+        'default' => "",
+    ],
+    [
+        'key' =>'power',
+        'export' =>'power',
+        'value' => '功率',
+        'required' => false,
+        'default' => "",
+        'unique' => false,
+        'is_main' => true,
+    ],
+    [
+        'key' =>'original_value',
+        'export' =>'original_value',
+        'value' => '原始价值',
+        'required' => false,
+        'default' => 0,
+        'unique' => false,
+        'is_main' => true,
+    ],
+    [
+        'key' =>'initial_value',
+        'export' =>'initial_value',
+        'value' => '期初价值',
+        'required' => true,
+        'default' => 0,
+        'unique' => false,
+        'is_main' => true,
+    ],
+    [
+        'key' =>'is_use',
+        'export' => 'is_use_title',
+        'value' => '是否启用',
+        'required' => true,
+        'default' => 1,
+        'unique' => false,
+        'is_main' => true,
+        'enums' => ['是', '否'],
+        'comments' => '必填'
+    ]
+];

+ 91 - 0
config/excel/item.php

@@ -0,0 +1,91 @@
+<?php
+return [
+    [
+        'key' =>'code',
+        'export' =>'code',
+        'value' => '项目编码',
+        'required' => true,
+        'is_main' => true,
+        'default' => "",
+        'unique' => true,
+        'enums' => [],
+        'comments' => '必填'
+    ],
+    [
+        'key' =>'title',
+        'export' =>'title',
+        'value' => '项目名称',
+        'required' => true,
+        'is_main' => true,
+        'default' => "",
+        'unique' => false,
+        'enums' => [],
+        'comments' => '必填'
+    ],
+    [
+        'key' =>'start_time',
+        'export' =>'start_time',
+        'value' => '项目开始日期',
+        'required' => true,
+        'is_main' => true,
+        'default' => "",
+        'unique' => false,
+        'enums' => [],
+        'comments' => '必填(如 2025-01-02)'
+    ],
+    [
+        'key' =>'end_time',
+        'export' =>'end_time',
+        'value' => '项目结束日期',
+        'required' => true,
+        'is_main' => true,
+        'default' => "",
+        'unique' => false,
+        'enums' => [],
+        'comments' => '必填(如 2025-01-31)'
+    ],
+    [
+        'key' =>'state',
+        'export' =>'state_title',
+        'value' => '状态',
+        'required' => true,
+        'is_main' => true,
+        'default' => "0",
+        'unique' => false,
+        'enums' => ["待开始", "进行中", "已完成"],
+        'comments' => '必填'
+    ],
+    [
+        'key' =>'mark',
+        'export' =>'mark',
+        'value' => '项目说明',
+        'required' => false,
+        'is_main' => true,
+        'unique' => false,
+        'enums' => [],
+        'default' => "",
+        'comments' => ""
+    ],
+    [
+        'key' =>'man_list',
+        'export' =>'man_list',
+        'value' => '人员',
+        'required' => true,
+        'is_main' => false,
+        'unique' => false,
+        'enums' => [],
+        'default' => "",
+        'comments' => '必填(人员工号,多人员用英文逗号隔开,如 001,002,003,004)'
+    ],
+    [
+        'key' =>'device_list',
+        'export' =>'device_list',
+        'value' => '设备',
+        'required' => true,
+        'default' => "",
+        'is_main' => false,
+        'unique' => false,
+        'enums' => [],
+        'comments' => '必填(资产编码,多设备用英文逗号隔开,如 001,002,003,004)'
+    ],
+];