index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. <template>
  2. <div class="app-container">
  3. <el-row :gutter="20">
  4. <!--用户数据-->
  5. <el-col style="display:flex" :span="20" :xs="24">
  6. <el-form :model="queryParams" ref="queryRef" :inline="true">
  7. <el-form-item label="企业信用代码" prop="unifiedSocialCreditCode">
  8. <el-input v-model="queryParams.unifiedSocialCreditCode" placeholder="请输入企业信用代码" clearable
  9. style="width: 240px" />
  10. </el-form-item>
  11. <el-form-item label="企业名称" prop="enterpriseName">
  12. <el-input v-model="queryParams.enterpriseName" placeholder="请输入企业名称" clearable
  13. style="width: 240px" />
  14. </el-form-item>
  15. <el-form-item>
  16. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  17. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  18. <el-button type="success" icon="Download" @click="handleBatchDownload" :disabled="selectedRows.length === 0">批量下载</el-button>
  19. </el-form-item>
  20. </el-form>
  21. </el-col>
  22. </el-row>
  23. <el-table stripe :data="tableData" style="width: 100%" height="710" @selection-change="handleSelectionChange">
  24. <el-table-column type="selection" width="55" />
  25. <el-table-column prop="enterpriseName" label="企业名称">
  26. </el-table-column>
  27. <el-table-column label="企业信用代码">
  28. <template #default="scope">
  29. <el-link v-if="store.getters.permissions.includes('*:*:*') || store.getters.permissions.includes('CEEnterprise:companydetail')" type="primary" @click="handleClick(scope.row)">{{ scope.row.unifiedSocialCreditCode }}</el-link>
  30. <div v-else>{{ scope.row.unifiedSocialCreditCode }}</div>
  31. </template>
  32. </el-table-column>
  33. <el-table-column prop="total" label="总分" align="center" width="100">
  34. <template #default="scope">
  35. {{ scope.row.total.toFixed(2) }}
  36. </template>
  37. </el-table-column>
  38. <el-table-column prop="grade" label="等级" width="100">
  39. </el-table-column>
  40. <el-table-column label="操作">
  41. <template #default="scope">
  42. <el-button @click="handlePreview(scope.row)" type="warning">预览企业二维码</el-button>
  43. <el-button @click="handleDownload(scope.row)" type="primary" :loading="downloadingRows.has(scope.row.unifiedSocialCreditCode)">
  44. 下载企业二维码
  45. </el-button>
  46. </template>
  47. </el-table-column>
  48. </el-table>
  49. <div style="position: fixed;bottom: 20px;right: 10px;">
  50. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
  51. v-model:limit="queryParams.pageSize" @pagination="getList" />
  52. </div>
  53. <el-dialog v-model="dialogQrcodeVisible" title="二维码预览" width="340px">
  54. <template #default>
  55. <div ref="qrcodeExport">
  56. <el-image style="width: 300px; height: 300px; margin: 0 auto;" :src="previewQrcodeSrc" />
  57. <div style="text-align: center;font-size: 14px;">{{currentPreviewInfo.unifiedSocialCreditCode}}</div>
  58. <div style="text-align: center;font-size: 16px;">{{currentPreviewInfo.enterpriseName}}</div>
  59. </div>
  60. </template>
  61. </el-dialog>
  62. <!-- 隐藏的canvas用于生成二维码 -->
  63. <canvas ref="hiddenCanvas" style="display: none;"></canvas>
  64. </div>
  65. </template>
  66. <script setup name="getCerating">
  67. import { ref, reactive, toRefs, onMounted } from 'vue';
  68. import { ElMessage, ElLoading } from 'element-plus';
  69. import { getCreditScoreList } from '@/api/ceenterprise/cerating';
  70. import { useRouter } from "vue-router";
  71. import store from "@/store";
  72. import QRCodeStyling from "qr-code-styling";
  73. import logo from '/public/favicon.ico';
  74. const router = useRouter();
  75. const site_url = import.meta.env.VITE_APP_SITE_URL;
  76. const currentPreviewInfo = ref({});
  77. const dialogQrcodeVisible = ref(false);
  78. const previewQrcodeSrc = ref('');
  79. const tableData = ref([]);
  80. const total = ref(0);
  81. const selectedRows = ref([]);
  82. const qrcodeExport = ref(null);
  83. const hiddenCanvas = ref(null);
  84. const downloadingRows = ref(new Set()); // 跟踪正在下载的行
  85. const data = reactive({
  86. form: {},
  87. queryParams: {
  88. pageNum: 1,
  89. pageSize: 20,
  90. unifiedSocialCreditCode: '',
  91. grade: null,
  92. total: null,
  93. enterpriseName: '',
  94. },
  95. });
  96. const {queryParams} = toRefs(data);
  97. // Initial data fetch on component mount
  98. onMounted(() => {
  99. getList();
  100. });
  101. /** 搜索按钮操作 */
  102. function handleQuery() {
  103. queryParams.value.pageNum = 1;
  104. getList();
  105. }
  106. /** 重置按钮操作 */
  107. function resetQuery() {
  108. queryParams.value.unifiedSocialCreditCode = "";
  109. queryParams.value.enterpriseName = "";
  110. handleQuery();
  111. }
  112. /** 查询用户列表 */
  113. function getList() {
  114. getCreditScoreList(queryParams.value).then((res) => {
  115. tableData.value = res.data.result;
  116. total.value = res.data.totalSize;
  117. }).catch(error => {
  118. console.error("获取列表失败:", error);
  119. ElMessage.error("获取列表失败,请稍后再试。");
  120. });
  121. }
  122. /** 处理行点击,跳转到详情页 */
  123. const handleClick = (row) => {
  124. const {unifiedSocialCreditCode, enterpriseName} = row;
  125. router.push({
  126. path: '/CEEnterprise/companydetail',
  127. query: {
  128. unifiedSocialCreditCode,
  129. enterpriseName
  130. }
  131. });
  132. };
  133. /** 生成二维码并返回canvas */
  134. const generateQRCode = async (row) => {
  135. return new Promise((resolve, reject) => {
  136. try {
  137. const qrCode = new QRCodeStyling({
  138. width: 600,
  139. height: 600,
  140. type: "canvas",
  141. data: site_url + '/mobile/companyPreview?uniCode=' + row.unifiedSocialCreditCode,
  142. image: logo,
  143. dotsOptions: {
  144. color: "#000000",
  145. type: "rounded"
  146. },
  147. backgroundOptions: {
  148. color: "#e9ebee",
  149. },
  150. imageOptions: {
  151. crossOrigin: "anonymous",
  152. margin: 20
  153. }
  154. });
  155. // 创建一个临时的canvas容器
  156. const tempContainer = document.createElement('div');
  157. tempContainer.style.position = 'absolute';
  158. tempContainer.style.left = '-9999px';
  159. tempContainer.style.top = '-9999px';
  160. document.body.appendChild(tempContainer);
  161. qrCode.append(tempContainer);
  162. // 等待二维码生成完成
  163. setTimeout(() => {
  164. const canvas = tempContainer.querySelector('canvas');
  165. if (canvas) {
  166. // 创建最终的canvas,包含二维码和文字
  167. const finalCanvas = document.createElement('canvas');
  168. const ctx = finalCanvas.getContext('2d');
  169. // 设置最终canvas的尺寸(二维码 + 文字区域)
  170. finalCanvas.width = 600;
  171. finalCanvas.height = 700; // 增加高度以容纳文字
  172. // 设置白色背景
  173. ctx.fillStyle = '#ffffff';
  174. ctx.fillRect(0, 0, finalCanvas.width, finalCanvas.height);
  175. // 绘制二维码
  176. ctx.drawImage(canvas, 0, 0);
  177. // 绘制文字
  178. ctx.fillStyle = '#000000';
  179. ctx.textAlign = 'center';
  180. // 绘制企业信用代码
  181. ctx.font = '24px Arial';
  182. ctx.fillText(row.unifiedSocialCreditCode, 300, 640);
  183. // 绘制企业名称
  184. ctx.font = 'bold 28px Arial';
  185. ctx.fillText(row.enterpriseName, 300, 680);
  186. // 清理临时元素
  187. document.body.removeChild(tempContainer);
  188. resolve(finalCanvas);
  189. } else {
  190. document.body.removeChild(tempContainer);
  191. reject(new Error('无法生企业二维码'));
  192. }
  193. }, 1000); // 给足够时间让二维码生成
  194. } catch (error) {
  195. reject(error);
  196. }
  197. });
  198. };
  199. /** 下载canvas为图片 */
  200. const downloadCanvas = (canvas, filename) => {
  201. try {
  202. const dataURL = canvas.toDataURL('image/png', 1.0);
  203. const link = document.createElement('a');
  204. link.href = dataURL;
  205. link.download = filename;
  206. document.body.appendChild(link);
  207. link.click();
  208. document.body.removeChild(link);
  209. } catch (error) {
  210. console.error('下载失败:', error);
  211. throw error;
  212. }
  213. };
  214. /** 处理下载单个二维码 */
  215. const handleDownload = async (row) => {
  216. const creditCode = row.unifiedSocialCreditCode;
  217. if (downloadingRows.value.has(creditCode)) {
  218. return; // 防止重复下载
  219. }
  220. downloadingRows.value.add(creditCode);
  221. try {
  222. const canvas = await generateQRCode(row);
  223. const filename = `${row.enterpriseName}_${creditCode}_二维码.png`;
  224. downloadCanvas(canvas, filename);
  225. ElMessage.success('二维码下载成功!');
  226. } catch (error) {
  227. console.error('下载二维码失败:', error);
  228. ElMessage.error('下载二维码失败,请稍后再试。');
  229. } finally {
  230. downloadingRows.value.delete(creditCode);
  231. }
  232. };
  233. /** 处理预览单个二维码 */
  234. const handlePreview = (row) => {
  235. currentPreviewInfo.value = row;
  236. const w = new QRCodeStyling({
  237. width: 600,
  238. height: 600,
  239. type: "svg",
  240. data: site_url + '/mobile/companyPreview?uniCode=' + row.unifiedSocialCreditCode,
  241. image: logo,
  242. dotsOptions: {
  243. color: "#000000",
  244. type: "rounded"
  245. },
  246. backgroundOptions: {
  247. color: "#e9ebee",
  248. },
  249. imageOptions: {
  250. crossOrigin: "anonymous",
  251. margin: 20
  252. }
  253. });
  254. w.getRawData().then(blob => {
  255. previewQrcodeSrc.value = URL.createObjectURL(blob);
  256. });
  257. dialogQrcodeVisible.value = true;
  258. };
  259. /** 处理表格行选择变化 */
  260. const handleSelectionChange = (selection) => {
  261. selectedRows.value = selection;
  262. };
  263. /** 批量下载二维码 */
  264. const handleBatchDownload = async () => {
  265. if (selectedRows.value.length === 0) {
  266. ElMessage.warning('请至少选择一个企业进行下载!');
  267. return;
  268. }
  269. const loadingInstance = ElLoading.service({
  270. fullscreen: true,
  271. text: `正在下载 ${selectedRows.value.length} 个二维码...`,
  272. background: 'rgba(0, 0, 0, 0.3)'
  273. });
  274. let successCount = 0;
  275. let failCount = 0;
  276. try {
  277. // 使用 Promise.allSettled 来处理并发下载,但限制并发数量
  278. const batchSize = 3; // 每批处理3个,避免浏览器卡顿
  279. for (let i = 0; i < selectedRows.value.length; i += batchSize) {
  280. const batch = selectedRows.value.slice(i, i + batchSize);
  281. const promises = batch.map(async (row) => {
  282. try {
  283. const canvas = await generateQRCode(row);
  284. const filename = `${row.enterpriseName}_${row.unifiedSocialCreditCode}_二维码.png`;
  285. downloadCanvas(canvas, filename);
  286. return {success: true, row};
  287. } catch (error) {
  288. console.error(`下载 ${row.enterpriseName} 的二维码失败:`, error);
  289. return {success: false, row, error};
  290. }
  291. });
  292. const results = await Promise.allSettled(promises);
  293. results.forEach((result) => {
  294. if (result.status === 'fulfilled' && result.value.success) {
  295. successCount++;
  296. } else {
  297. failCount++;
  298. }
  299. });
  300. // 更新加载文本
  301. loadingInstance.setText(`正在下载二维码... (${successCount + failCount}/${selectedRows.value.length})`);
  302. // 批次间稍作延迟,避免浏览器卡顿
  303. if (i + batchSize < selectedRows.value.length) {
  304. await new Promise(resolve => setTimeout(resolve, 500));
  305. }
  306. }
  307. // 显示结果消息
  308. if (failCount === 0) {
  309. ElMessage.success(`成功下载 ${successCount} 个企业二维码!`);
  310. } else if (successCount > 0) {
  311. ElMessage.warning(`下载完成:成功 ${successCount} 个,失败 ${failCount} 个`);
  312. } else {
  313. ElMessage.error('批量下载失败,请稍后再试。');
  314. }
  315. } catch (error) {
  316. console.error("批量下载过程中发生错误:", error);
  317. ElMessage.error("批量下载过程中发生错误。");
  318. } finally {
  319. loadingInstance.close();
  320. }
  321. };
  322. </script>
  323. <style scoped>
  324. .app-container {
  325. padding: 20px;
  326. }
  327. /* 添加一些加载状态的样式 */
  328. .el-button.is-loading {
  329. pointer-events: none;
  330. }
  331. </style>