Import.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. namespace App\Import;
  3. use App\Service\ImportService;
  4. use Illuminate\Support\Facades\DB;
  5. use Maatwebsite\Excel\Concerns\ToArray;
  6. use Maatwebsite\Excel\Concerns\WithMultipleSheets;
  7. use Maatwebsite\Excel\Facades\Excel;
  8. class Import implements WithMultipleSheets,ToArray {
  9. private $msg = '';
  10. public $crt_id = 0;
  11. public $config = [];
  12. public $mergedCells = [];
  13. public $data = [];
  14. public function sheets(): array
  15. {
  16. return [
  17. 0 => $this, // 指定要导入的 sheet 索引,这里假设是第一个 sheet
  18. ];
  19. }
  20. public function array (array $array){
  21. $this->handleData($array);
  22. }
  23. public function setConfig($config,$mergedCells,$data){
  24. $this->config = $config;
  25. $this->mergedCells = $mergedCells;
  26. $this->data = $data;
  27. }
  28. public function getMsg(){
  29. return $this->msg;
  30. }
  31. public function setMsg($msg){
  32. $this->msg = $msg;
  33. }
  34. public function handleData (array $array) {
  35. //表头处理
  36. list($status,$msg) = $this->clearHead($array);
  37. if(! $status) {
  38. $this->setMsg($msg);
  39. return ;
  40. }
  41. //表头
  42. $table_head = $msg;
  43. // 去除表头
  44. unset($array[0]);
  45. if(empty($array)) {
  46. $this->setMsg('导入数据不能为空!');
  47. return ;
  48. }
  49. //数据处理
  50. list($status,$msg) = $this->checkRule($table_head,$array);
  51. if(! $status) {
  52. $this->setMsg($msg);
  53. return ;
  54. }
  55. //写入数据
  56. $time = time();
  57. list($status,$msg) = $this->insertAllData($msg, $time);
  58. if(! $status) {
  59. $this->setMsg($msg);
  60. return ;
  61. }
  62. //(特殊 需要额外写入的数据) 做对写入数据的更新
  63. if(! empty($this->config['other_field_func'])){
  64. $func = $this->config['other_field_func'] ?? "";
  65. if(empty($func)) return;
  66. (new ImportService)->$func($time);
  67. }
  68. }
  69. public function clearHead($array){
  70. $table_head = array_filter($array[0]);
  71. if(empty($table_head)) return [false, '表头不能为空!'];
  72. foreach ($table_head as $key => $value){
  73. $head_tmp = trim($value);
  74. if(empty($head_tmp)) {
  75. unset($table_head[$key]);
  76. }else{
  77. $table_head[$key] = $head_tmp;
  78. }
  79. }
  80. if(empty($table_head)) return [false, '表头不能为空!'];
  81. return [true, $table_head];
  82. }
  83. public function insertAllData($data, $time){
  84. $model_array = $this->config['table'];
  85. //执行写入
  86. try{
  87. DB::beginTransaction();
  88. foreach ($model_array as $model_name => $model_field){
  89. if(! empty($model_field['need_param'])) {
  90. $model = "\App\Model\\" . $model_field['db_name'];
  91. $last_insert_id = $model::where('crt_time',$time)
  92. ->select($model_field['db_key'])
  93. ->get()->toArray();
  94. $last_insert_id = array_column($last_insert_id,$model_field['db_key']);
  95. }
  96. $tmp = [];$insert = [];
  97. if(! empty($model_field['field'])){
  98. foreach ($model_field['field'] as $m){
  99. $tmp[$m] = "";
  100. if(strstr($m,'time')) $tmp[$m] = $time;
  101. if($m == "depart_id") $tmp[$m] = $this->data['depart_id'] ?? 0;
  102. if($m == "top_depart_id") $tmp[$m] = $this->data['top_depart_id'] ?? 0;
  103. if($m == "crt_id") $tmp[$m] = $this->data['user_id'] ?? 0;
  104. }
  105. foreach ($data as $value){
  106. $insert[] = array_intersect_key(array_merge($tmp, $value),$tmp);
  107. }
  108. }elseif (! empty($model_field['field_array'])){
  109. foreach ($model_field['field_array'] as $km => $m){
  110. foreach ($m as $v){
  111. $tmp[$v] = "";
  112. if(strstr($v,'time')) $tmp[$v] = $time;
  113. }
  114. foreach ($data as $key => $value){
  115. foreach ($value[$km] as $vv){
  116. $vv[$model_field['need_param']] = $last_insert_id[$key];
  117. $insert[] = array_intersect_key(array_merge($tmp, $vv),$tmp);
  118. }
  119. }
  120. }
  121. }
  122. $model = "\App\Model\\" . $model_name;
  123. $model::insert($insert);
  124. }
  125. DB::commit();
  126. }catch (\Exception $e){
  127. DB::rollBack();
  128. return [false, $e->getMessage().$e->getLine()];
  129. }
  130. return [true, ''];
  131. }
  132. public function checkRule($head, $data){
  133. //模板里的表头
  134. foreach ($this->config['field'] as $key => $value){
  135. if(! in_array($key, $head)) return [false, "缺少表头:" . $key];
  136. }
  137. //处理 =》 存在合并单元格数据
  138. if(! empty($this->mergedCells)) $data = $this->clearMergeData($data);
  139. //数据校验
  140. list($status,$msg) = $this->checkData($head,$data);
  141. return [$status,$msg];
  142. }
  143. function checkData($head,$data){
  144. $return_data = [];
  145. //数据处理
  146. foreach ($this->config['field'] as $key => $value){
  147. //找到表头在xlsx中的下标位置
  148. $key2 = array_search($key, $head);
  149. if ($key2 === false) return [false, '未找到表头在xlsx中的位置!'];
  150. //获取表头对应列数据
  151. $tmp_data = array_column($data,$key2);
  152. //去除数据中两侧的空白字符或其他预定义字符
  153. foreach ($tmp_data as $v_key => $v_data){
  154. $tmp_data[$v_key] = trim($v_data);
  155. }
  156. //------ 根据规则 查找数据 =>做 替换和校验--------------//
  157. //校验数据唯一
  158. if (strpos($value['other_rule'], 'unique') !== false) {
  159. $uniqueIndex = strpos($value['other_rule'], 'unique:');
  160. $start = $uniqueIndex + strlen('unique:');
  161. $end = strpos($value['other_rule'], '|', $start);
  162. if ($end !== false) {
  163. $uniqueValue = substr($value['other_rule'], $start, $end - $start);
  164. } else {
  165. $uniqueValue = substr($value['other_rule'], $start);
  166. }
  167. $model = "\App\Model\\" . $uniqueValue;
  168. $bool = $model::whereIn($value['key'], array_unique($tmp_data))
  169. ->where('del_time',0)
  170. ->exists();
  171. if($bool) return [false, $key . "在数据库中已存在"];
  172. }
  173. //替换数据
  174. if(! empty($value['db_search'])){
  175. $db = $value['db_search'];
  176. $model = "\App\Model\\" . $db['db_name'];
  177. $map = $model::whereIn($db['key'], array_unique($tmp_data))
  178. ->pluck($db['value'], $db['key'])
  179. ->toArray();
  180. }
  181. //------ 根据规则 查找数据 =>做 替换和校验--------------//
  182. //校验&&组织
  183. $tmp_clean = [];
  184. foreach ($tmp_data as $v_key => $v_data){
  185. if (strpos($value['other_rule'], 'require') !== false) {
  186. if(empty($v_data)) return [false, $key . '不能为空!'];
  187. }
  188. if (strpos($value['other_rule'], 'unique') !== false) {
  189. if(in_array($v_data,$tmp_clean)) return [false, $key . '不能重复!'];
  190. $tmp_clean[] = $v_data;
  191. }
  192. if (strpos($value['other_rule'], 'is_numeric') !== false && ! empty($v_data)) {
  193. if(! is_numeric($v_data)) return [false, $key . '请输入数字!'];
  194. $formattedNumber = number_format($v_data, 2, '.', '');
  195. if($formattedNumber != $v_data) return [false, $key . '请输入数字(请输入不超过两位小数)!'];
  196. }
  197. if(! empty($value['db_search'])) {
  198. if(empty($map[$v_data])) return [false, $key . '下所输入的内容在系统中不存在!'];
  199. $v_data = $map[$v_data];
  200. }
  201. if(! empty($value['multiple'])) {
  202. $field = $value['map'][$key] ?? "";
  203. $value['key_array'][$field] = $v_data;
  204. //子表数据 一对多
  205. $return_data[$v_key][$value['key']][] = $value['key_array'];
  206. }else{
  207. //数据
  208. $return_data[$v_key][$value['key']] = $v_data;
  209. }
  210. }
  211. }
  212. unset($tmp_clean);
  213. return [true, $return_data];
  214. }
  215. // 处理合并单元格数据
  216. function clearMergeData($data){
  217. //获取行列
  218. $rows = count($data);
  219. $cols = count($data[1]);
  220. //非合并单元格并且为null的数据 替换成空字符串
  221. for ($row = 1; $row < $rows; $row++) {
  222. // 遍历每一列
  223. for ($col = 0; $col < $cols; $col++) {
  224. // 如果当前值为 null,并且不是合并单元格
  225. if ($data[$row][$col] === null && ! $this->isMergedCell($this->mergedCells, $row, $col)) {
  226. $data[$row][$col] = '';
  227. } else {
  228. $data[$row][$col] = $data[$row][$col];
  229. }
  230. }
  231. }
  232. //合并单元格数据替换为原来的数据
  233. for ($row = 1; $row <= $rows; $row++) {
  234. // 遍历每一列
  235. for ($col = 0; $col < $cols; $col++) {
  236. // 如果当前值为 null
  237. if ($data[$row][$col] === null) {
  238. // 查找左方第一个非 null 值
  239. for ($i = $col - 1; $i >= 0; $i--) {
  240. if ($data[$row][$i] !== null) {
  241. $data[$row][$col] = $data[$row][$i];
  242. break;
  243. }
  244. }
  245. // 如果左方没有非 null 值,则查找上方第一个非 null 值
  246. if (!isset($newArray[$row][$col])) {
  247. for ($i = $row - 1; $i >= 1; $i--) {
  248. if ($data[$i][$col] !== null) {
  249. $data[$row][$col] = $data[$i][$col];
  250. break;
  251. }
  252. }
  253. }
  254. } else {
  255. $data[$row][$col] = $data[$row][$col];
  256. }
  257. }
  258. }
  259. return $data;
  260. }
  261. // 判断一个单元格是否是合并单元格
  262. function isMergedCell($mergeCells, $row, $col) {
  263. foreach ($mergeCells as $mergeCell) {
  264. [$start, $end] = explode(':', $mergeCell);
  265. [$startCol, $startRow] = $this->coordinateToIndex($start);
  266. [$endCol, $endRow] = $this->coordinateToIndex($end);
  267. if ($col >= $startCol && $col <= $endCol && $row >= $startRow && $row <= $endRow) {
  268. return true;
  269. }
  270. }
  271. return false;
  272. }
  273. // 将 Excel 单元格坐标转换为索引
  274. function coordinateToIndex($coordinate) {
  275. preg_match('/([A-Z]+)(\d+)/', $coordinate, $matches);
  276. [, $col, $row] = $matches;
  277. $colIndex = 0;
  278. $length = strlen($col);
  279. for ($i = 0; $i < $length; $i++) {
  280. $colIndex += (ord($col[$i]) - 65 + 1) * pow(26, $length - $i - 1);
  281. }
  282. return [$colIndex - 1, $row - 1];
  283. }
  284. }