Laravel Excel 上传和进度条

Kai*_*all 2 php ajax excel laravel progress-bar

我有一个网站,我可以在其中上传一个.xlsx文件,其中包含我的数据库的一些信息行。我阅读了 laravel-excel 的文档,但如果您使用控制台方法,它看起来只适用于进度条;我不这么认为。

我目前只使用纯 HTML 上传表单,还没有使用 ajax。

但要为此创建这个进度条,我需要将其转换为 ajax,这并不麻烦,我可以做到。

但是,在上传文件并遍历 Excel 文件中的每一行时,如何创建进度条呢?

这是完成上传的控制器和方法:

/**
 * Import companies
 *
 * @param Import $request
 * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
 */
public function postImport(Import $request)
{
    # Import using Import class
    Excel::import(new CompaniesImport, $request->file('file'));

    return redirect(route('dashboard.companies.index.get'))->with('success', 'Import successfull!');
}
Run Code Online (Sandbox Code Playgroud)

这是导入文件:

public function model(array $row)
{
    # Don't create or validate on empty rows
    # Bad workaround
    # TODO: better solution
    if (!array_filter($row)) {
        return null;
    }

    # Create company
    $company = new Company;
    $company->crn = $row['crn'];
    $company->name = $row['name'];
    $company->email = $row['email'];
    $company->phone = $row['phone'];
    $company->website = (!empty($row['website'])) ? Helper::addScheme($row['website']) : '';
    $company->save();

    # Everything empty.. delete address
    if (!empty($row['country']) || !empty($row['state']) || !empty($row['postal']) || !empty($row['address']) || !empty($row['zip'])) {

        # Create address
        $address = new CompanyAddress;
        $address->company_id = $company->id;
        $address->country = $row['country'];
        $address->state = $row['state'];
        $address->postal = $row['postal'];
        $address->address = $row['address'];
        $address->zip = $row['zip'];
        $address->save();

        # Attach
        $company->addresses()->save($address);

    }

    return $company;

}
Run Code Online (Sandbox Code Playgroud)

我知道目前这还不算什么。我只需要一些帮助来弄清楚如何创建这个进度条,因为我陷入了困境。

我的想法是创建一个ajax上传表单,但从那里我不知道。

mer*_*ayi 6

\n

只是一个想法,但您可以在导入执行期间使用 Laravel 会话来存储total_row_count 和processed_row_count。然后,您可以在 setInterval() 上创建一个单独的 AJAX 调用来轮询这些会话值(例如,每秒一次)。这将允许您将进度计算为processed_row_count/total_row_count,并输出到可视进度条。\xe2\x80\x93 马蒂卡斯塔德

\n
\n

将@matticustard 评论付诸实践。以下只是如何实施的示例,也许还有需要改进的地方。

\n

1. 路由
\nimport初始化 Excel 导入的路由。
\nimport-status路由将用于获取最新的导入状态

\n
Route::post(\'import\', [ProductController::class, \'import\']);\nRoute::get(\'import-status\', [ProductController::class, \'status\']);\n
Run Code Online (Sandbox Code Playgroud)\n

2. 控制器
\nimport操作将验证上传的文件,并传递$idProductsImport类。由于它将在后台排队并运行,因此无法访问当前会话。cache我们将在后台使用。如果要处理更多的并发导入,最好生成更多的随机数据$id,为了保持简单,现在只使用 unix 日期。

\n
\n

您当前无法对 xls 导入进行排队。PhpSpreadsheet 的 Xls 阅读器包含一些非 utf8 字符,导致无法排队。
\n XLS 导入无法排队

\n
\n
public function import()\n{\n    request()->validate([\n        \'file\' => [\'required\', \'mimes:xlsx\'],\n    ]);\n\n    $id = now()->unix()\n    session([ \'import\' => $id ]);\n\n    Excel::queueImport(new ProductsImport($id), request()->file(\'file\')->store(\'temp\'));\n\n    return redirect()->back();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

从缓存中获取从会话传递的最新导入状态$id

\n
public function status()\n{\n    $id = session(\'import\');\n\n    return response([\n        \'started\' => filled(cache("start_date_$id")),\n        \'finished\' => filled(cache("end_date_$id")),\n        \'current_row\' => (int) cache("current_row_$id"),\n        \'total_rows\' => (int) cache("total_rows_$id"),\n    ]);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

3. 导入类
\n使用WithEvents BeforeImport我们将 excel 文件的总行数设置到缓存中。使用onRow我们将当前处理的行设置到缓存。并AfterReset清除所有数据。

\n
<?php\n\nnamespace App\\Imports;\n\nuse App\\Models\\Product;\nuse Maatwebsite\\Excel\\Row;\nuse Maatwebsite\\Excel\\Concerns\\OnEachRow;\nuse Maatwebsite\\Excel\\Events\\AfterImport;\nuse Maatwebsite\\Excel\\Events\\BeforeImport;\nuse Maatwebsite\\Excel\\Concerns\\WithEvents;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Maatwebsite\\Excel\\Concerns\\WithStartRow;\nuse Maatwebsite\\Excel\\Concerns\\WithChunkReading;\nuse Maatwebsite\\Excel\\Concerns\\WithMultipleSheets;\n\nclass ProductsImport implements OnEachRow, WithEvents, WithChunkReading, ShouldQueue\n{\n    public $id;\n\n    public function __construct(int $id)\n    {\n        $this->id = $id;\n    }\n\n    public function chunkSize(): int\n    {\n        return 100;\n    }\n\n    public function registerEvents(): array\n    {\n        return [\n            BeforeImport::class => function (BeforeImport $event) {\n                $totalRows = $event->getReader()->getTotalRows();\n\n                if (filled($totalRows)) {\n                    cache()->forever("total_rows_{$this->id}", array_values($totalRows)[0]);\n                    cache()->forever("start_date_{$this->id}", now()->unix());\n                }\n            },\n            AfterImport::class => function (AfterImport $event) {\n                cache(["end_date_{$this->id}" => now()], now()->addMinute());\n                cache()->forget("total_rows_{$this->id}");\n                cache()->forget("start_date_{$this->id}");\n                cache()->forget("current_row_{$this->id}");\n            },\n        ];\n    }\n\n    public function onRow(Row $row)\n    {\n        $rowIndex = $row->getIndex();\n        $row      = array_map(\'trim\', $row->toArray());\n        cache()->forever("current_row_{$this->id}", $rowIndex);\n        // sleep(0.2);\n\n        Product::create([ ... ]);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

4. 前端
\n在前端,这只是如何处理事情的示例。这里我用了vuejs,ant-design-vuelodash

\n
    \n
  • 上传文件后handleChange调用方法
  • \n
  • trackProgress第一次调用成功上传方法
  • \n
  • trackProgress方法是递归函数,完成后调用自身
  • \n
  • 使用 lodash _.debounce方法,我们可以防止过多调用它
  • \n
\n
export default {\n  data() {\n    this.trackProgress = _.debounce(this.trackProgress, 1000);\n\n    return {\n      visible: true,\n      current_row: 0,\n      total_rows: 0,\n      progress: 0,\n    };\n  },\n\n  methods: {\n    handleChange(info) {\n      const status = info.file.status;\n\n      if (status === "done") {\n        this.trackProgress();\n      } else if (status === "error") {\n        this.$message.error(_.get(info, \'file.response.errors.file.0\', `${info.file.name} file upload failed.`));\n      }\n    },\n\n    async trackProgress() {\n      const { data } = await axios.get(\'/import-status\');\n\n      if (data.finished) {\n        this.current_row = this.total_rows\n        this.progress = 100\n        return;\n      };\n\n      this.total_rows = data.total_rows;\n      this.current_row = data.current_row;\n      this.progress = Math.ceil(data.current_row / data.total_rows * 100);\n      this.trackProgress();\n    },\n\n    close() {\n      if (this.progress > 0 && this.progress < 100) {\n        if (confirm(\'Do you want to close\')) {\n          this.$emit(\'close\')\n          window.location.reload()\n        }\n      } else {\n        this.$emit(\'close\')\n        window.location.reload()\n      }\n    }\n  },\n};\n
Run Code Online (Sandbox Code Playgroud)\n

\r\n
\r\n
<template>\n  <a-modal\n    title="Upload excel"\n    v-model="visible"\n    cancel-text="Close"\n    ok-text="Confirm"\n    :closable="false"\n    :maskClosable="false"\n    destroyOnClose\n  >\n    <a-upload-dragger\n      name="file"\n      :multiple="false"\n      :showUploadList="false"\n      :action="`/import`"\n      @change="handleChange"\n    >\n      <p class="ant-upload-drag-icon">\n        <a-icon type="inbox" />\n      </p>\n      <p class="ant-upload-text">Click to upload</p>\n    </a-upload-dragger>\n    <a-progress class="mt-5" :percent="progress" :show-info="false" />\n    <div class="text-right mt-1">{{ this.current_row }} / {{ this.total_rows }}</div>\n    <template slot="footer">\n      <a-button @click="close">Close</a-button>\n    </template>\n  </a-modal>\n</template>\n\n<script>\nexport default {\n  data() {\n    this.trackProgress = _.debounce(this.trackProgress, 1000);\n\n    return {\n      visible: true,\n      current_row: 0,\n      total_rows: 0,\n      progress: 0,\n    };\n  },\n\n  methods: {\n    handleChange(info) {\n      const status = info.file.status;\n\n      if (status === "done") {\n        this.trackProgress();\n      } else if (status === "error") {\n        this.$message.error(_.get(info, \'file.response.errors.file.0\', `${info.file.name} file upload failed.`));\n      }\n    },\n\n    async trackProgress() {\n      const { data } = await axios.get(\'/import-status\');\n\n      if (data.finished) {\n        this.current_row = this.total_rows\n        this.progress = 100\n        return;\n      };\n\n      this.total_rows = data.total_rows;\n      this.current_row = data.current_row;\n      this.progress = Math.ceil(data.current_row / data.total_rows * 100);\n      this.trackProgress();\n    },\n\n    close() {\n      if (this.progress > 0 && this.progress < 100) {\n        if (confirm(\'Do you want to close\')) {\n          this.$emit(\'close\')\n          window.location.reload()\n        }\n      } else {\n        this.$emit(\'close\')\n        window.location.reload()\n      }\n    }\n  },\n};\n</script>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n