home.vue 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770
  1. <template>
  2. <div>
  3. <el-container style="height: 100vh;">
  4. <!-- #333744 -->
  5. <el-aside width="180px" style="user-select: none; border-right: 1px solid #fafafa; background-color: #333744; overflow-x: hidden;">
  6. <p class="soft-name" style="-webkit-app-region: drag; color: #fff;">
  7. <img src="../assets/image/icon.png" class="soft-icon" />
  8. <span style="letter-spacing: 1px;">{{productName}}</span>
  9. </p>
  10. <el-menu :default-openeds="['a', 'b']" :default-active="menuIndex" @select="setMenuIndex" active-text-color="#409EFF" background-color="#333744" text-color="#fff" style="margin-top: 10px;">
  11. <el-submenu index="a">
  12. <template slot="title"><img src="../assets/image/m-download.png" class="m-image"/>图片下载</template>
  13. <el-menu-item index="3">
  14. <img src="../assets/image/m-tmall.png" class="m-image"/><span slot="title">天猫</span>
  15. </el-menu-item>
  16. <el-menu-item index="2">
  17. <img src="../assets/image/m-jd.png" class="m-image"/><span slot="title">京东</span>
  18. </el-menu-item>
  19. <el-menu-item index="4">
  20. <img src="../assets/image/m-taobao.png" class="m-image"/><span slot="title">淘宝</span>
  21. </el-menu-item>
  22. <el-menu-item index="1">
  23. <img src="../assets/image/m-alibaba.png" class="m-image"/><span slot="title">阿里巴巴</span>
  24. </el-menu-item>
  25. <el-menu-item index="5">
  26. <img src="../assets/image/m-hong.png" class="m-image"/><span slot="title">小红书</span>
  27. </el-menu-item>
  28. <el-menu-item index="10">
  29. <img src="../assets/image/m-chrome.png" class="m-image"/><span slot="title">其他网址(Beta)</span>
  30. </el-menu-item>
  31. </el-submenu>
  32. <el-submenu index="b">
  33. <template slot="title"><img src="../assets/image/m-edit.png" class="m-image"/>图片处理</template>
  34. <el-menu-item index="2-1">
  35. <img src="../assets/image/m-geshi.png" class="m-image"/><span slot="title">格式转换</span>
  36. </el-menu-item>
  37. <el-menu-item index="2-2">
  38. <img src="../assets/image/m-yasuo.png" class="m-image"/><span slot="title">图片压缩</span>
  39. </el-menu-item>
  40. <el-menu-item index="2-3">
  41. <img src="../assets/image/m-chicun.png" class="m-image"/><span slot="title">修改尺寸</span>
  42. </el-menu-item>
  43. <el-menu-item index="2-5">
  44. <img src="../assets/image/m-shuiyin.png" class="m-image"/><span slot="title">添加水印</span>
  45. </el-menu-item>
  46. </el-submenu>
  47. </el-menu>
  48. </el-aside>
  49. <el-container>
  50. <el-header height="45px" style="background-color: #fafafa; padding: 0 10px;">
  51. <soft-header ref="headerRef" @update-soft="updateSoft()" @export-file="exportFile" @login-url="loginUrl" @clear-cache="clearCache"></soft-header>
  52. </el-header>
  53. <soft-img ref="imgRef" v-show="['2-1', '2-2', '2-3', '2-5'].indexOf(menuIndex) > -1" @open-vip="openVip" @check-authority="checkAuthority"></soft-img>
  54. <el-main v-show="['1', '2', '3', '4', '5', '10'].indexOf(menuIndex) > -1" ref="el-main" style="background-color: #fafafa;">
  55. <template>
  56. <div class="content-top">
  57. <div>
  58. <el-button-group>
  59. <el-button type="primary" size="mini" icon="el-icon-circle-plus-outline"
  60. @click="addVisible = true;">添加链接</el-button>
  61. <el-button type="primary" size="mini" icon="el-icon-upload"
  62. @click="pickLink()">导入链接</el-button>
  63. <el-button type="primary" size="mini" icon="el-icon-delete"
  64. @click="clearList()">清空链接</el-button>
  65. </el-button-group>
  66. <el-link type="info" style="margin-left: 20px; vertical-align:baseline;" @click="downloadExample()">模板下载<i class="el-icon-download"></i></el-link>
  67. </div>
  68. <el-row type="flex" style="align-items: center;">
  69. <div class="set-item">
  70. <span class="set-title">保存目录:</span>
  71. <el-input :title="downloadDir" ref="upload-input" size="mini" @focus="pickPath" placeholder="请选择输出目录" v-model="downloadDir" readonly style="width:200px;" prefix-icon="el-icon-folder"></el-input>
  72. <el-popover placement="bottom" popper-class="popper-open" trigger="hover" content="打开保存目录">
  73. <i class="el-icon-folder-opened" slot="reference" style="padding-left: 5px; cursor: pointer; font-size: 22px; vertical-align: middle;" @click="openFolder()"></i>
  74. </el-popover>
  75. </div>
  76. <el-button type="danger" @click="exportFile()" :loading="loading">开始下载</el-button>
  77. </el-row>
  78. </div>
  79. <div style="padding: 20px 20px 0 20px; height: calc(100% - 62px);">
  80. <el-row type="flex" justify="space-between">
  81. <div>
  82. <h3 style="display: inline-block;">
  83. <span v-if="menuIndex == '1'">阿里巴巴 - </span>
  84. <span v-if="menuIndex == '2'">京东 - </span>
  85. <span v-if="menuIndex == '3'">天猫 - </span>
  86. <span v-if="menuIndex == '4'">淘宝 - </span>
  87. <span v-if="menuIndex == '5'">小红书 - </span>
  88. <span v-if="menuIndex == '10'">网页 - </span>
  89. 图片下载
  90. </h3>
  91. <el-link v-if="menuIndex == '5'" :underline="false" type="danger" style="text-align: center; font-size: 12px;">
  92. 仅支持win10及以上系统,浏览器请选择最新版本
  93. </el-link>
  94. <!-- <el-link v-if="menuIndex == '3' || menuIndex == '4'" :underline="false" type="danger" style="text-align: center; font-size: 12px;">
  95. (需登录{{menuIndex == '3' ? '天猫' : '淘宝'}}账号后才能下载)
  96. </el-link>
  97. <el-link v-if="menuIndex == '2'" :underline="false" type="danger" style="text-align: center; font-size: 12px;">
  98. (需登录京东账号后才能下载)
  99. </el-link>
  100. <el-link v-if="menuIndex == '10'" :underline="false" type="info" style="text-align: center; font-size: 12px;">
  101. (非会员功能,仅提供试用)
  102. </el-link> -->
  103. </div>
  104. </el-row>
  105. <div style="padding: 15px 0 20px;">
  106. <el-row type="flex" justify="space-between">
  107. <div v-if="menuIndex == '5'" style="padding-top: 10px;">
  108. <label>下载类型:</label>
  109. <el-checkbox :value="true" style="opacity: 0.6; cursor: not-allowed;">文章图/视频</el-checkbox>
  110. <el-popover placement="bottom" popper-class="popper-open" trigger="hover" content="实况(Live)图下载的格式是mp4,且同时下载任务量必须为1个">
  111. <i class="el-icon-info" slot="reference" style="margin-left: 10px; color: #F56C6C;"></i>
  112. </el-popover>
  113. </div>
  114. <div v-if="menuIndex == '10'" style="padding-top: 10px;">
  115. <label>下载类型:</label>
  116. <el-checkbox :value="true" style="opacity: 0.6; cursor: not-allowed;">图片</el-checkbox>
  117. </div>
  118. <div v-if="['1', '2', '3', '4'].indexOf(menuIndex) > -1" style="padding-top: 10px;">
  119. <label>下载类型:</label>
  120. <el-checkbox-group :min="1" v-model="settingArr" style="display: inline-block;" @input="settingGroup">
  121. <el-checkbox label="mainImg">主图</el-checkbox>
  122. <el-checkbox label="detailImg">详情图</el-checkbox>
  123. <el-checkbox label="skuImg">SKU图</el-checkbox>
  124. <el-checkbox label="commentImg" v-if="menuIndex == '3' || menuIndex == '4'">评论图</el-checkbox>
  125. <el-checkbox label="video">视频</el-checkbox>
  126. </el-checkbox-group>
  127. <el-popover placement="bottom" popper-class="popper-open" trigger="hover" content="下载类型至少选一个,评论图默认只下载商品首页展示的评论内容">
  128. <i class="el-icon-info" slot="reference" style="margin-left: 10px; color: #F56C6C;"></i>
  129. </el-popover>
  130. </div>
  131. <!-- 阿里巴巴账号 -->
  132. <template v-if="menuIndex == '1'">
  133. <div>
  134. <el-tag type="info" size="mini" v-if="alibabaStatus == 1">未检测</el-tag>
  135. <el-tag type="success" size="mini" v-if="alibabaStatus == 2">阿里巴巴账号已登录</el-tag>
  136. <el-link type="danger" style="text-decoration: underline;" v-if="alibabaStatus == 3" :underline="false" @click="loginUrl('https://www.1688.com')">未登录,点击登录阿里巴巴账号</el-link>
  137. <el-button size="mini" type="warning" :loading="checkLoading" style="margin-left: 10px;" :disabled='alibabaStatus == 2' @click="checkAlibabaLogin">检测登录状态</el-button>
  138. </div>
  139. </template>
  140. <!-- 天猫/淘宝 -->
  141. <template v-if="menuIndex == '3' || menuIndex == '4'">
  142. <div>
  143. <el-tag type="info" size="mini" v-if="tbStatus == 1">未检测</el-tag>
  144. <el-tag type="success" size="mini" v-if="tbStatus == 2">{{menuIndex == '3' ? '天猫' : '淘宝'}}账号已登录</el-tag>
  145. <el-link type="danger" style="text-decoration: underline;" v-if="tbStatus == 3" :underline="false" @click="loginUrl('https://login.taobao.com')">未登录,点击登录<span>{{menuIndex == '3' ? '天猫' : '淘宝'}}</span>账号</el-link>
  146. <el-button size="mini" type="warning" :loading="checkLoading" style="margin-left: 10px;" :disabled='tbStatus == 2' @click="checkLogin">检测登录状态</el-button>
  147. </div>
  148. </template>
  149. <!-- 京东账号 -->
  150. <template v-if="menuIndex == '2'">
  151. <div>
  152. <el-tag type="info" size="mini" v-if="jdStatus == 1">未检测</el-tag>
  153. <el-tag type="success" size="mini" v-if="jdStatus == 2">京东账号已登录</el-tag>
  154. <el-link type="danger" style="text-decoration: underline;" v-if="jdStatus == 3" :underline="false" @click="loginUrl('https://passport.jd.com/new/login.aspx')">未登录,点击登录京东账号</el-link>
  155. <el-button size="mini" type="warning" :loading="checkLoading" style="margin-left: 10px;" :disabled='jdStatus == 2' @click="checkJdLogin">检测登录状态</el-button>
  156. </div>
  157. </template>
  158. <!-- 小红书 -->
  159. <template v-if="menuIndex == '5'">
  160. <div>
  161. <el-tag type="info" size="mini" v-if="redStatus == 1">未检测</el-tag>
  162. <el-tag type="success" size="mini" v-if="redStatus == 2">小红书账号已登录</el-tag>
  163. <el-link type="danger" style="text-decoration: underline;" v-if="redStatus == 3" :underline="false" @click="loginUrl('https://www.xiaohongshu.com')">未登录,点击登录小红书账号</el-link>
  164. <el-button size="mini" type="warning" :loading="checkLoading" style="margin-left: 10px;" :disabled='redStatus == 2' @click="checkRedLogin">检测登录状态</el-button>
  165. </div>
  166. </template>
  167. </el-row>
  168. </div>
  169. <div class="table-scroll">
  170. <!-- 1、 -->
  171. <vxe-table ref="xTable" show-overflow class="img-table" max-height="100%" empty-text="没有更多数据了!" :loading="tabLoading" :row-config="{isHover: true}"
  172. :loading-config="{icon: 'vxe-icon-indicator roll', text: '列表加载中...'}" :data="this[listStr+'List']" :scroll-y="{enabled: true}">
  173. <vxe-column type="seq" width="60"></vxe-column>
  174. <vxe-column field="title" title="目录名称" width="200">
  175. <template #default="{ row, rowIndex }">
  176. <span v-if="row.title">{{row.title}}</span>
  177. <el-tag size="mini" v-else>默认使用网页标题</el-tag>
  178. </template>
  179. </vxe-column>
  180. <vxe-column field="url" title="网页链接"></vxe-column>
  181. <vxe-column field="status" title="下载状态" width="200">
  182. <template #default="{ row }">
  183. <template v-if="row.status == '1'">
  184. <i class="el-icon-info" style="font-size: 16px; color: #999;"></i>
  185. <span>待操作</span>
  186. </template>
  187. <template v-if="row.status == '2'">
  188. <i class="el-icon-loading" style="font-size: 16px; color: #999;"></i>
  189. <span>任务处理中...</span>
  190. </template>
  191. <template v-if="row.status == '3'">
  192. <i class="el-icon-loading" style="font-size: 16px; color: #999;"></i>
  193. <span>图片下载中..<span v-if="row.num > 0">第{{row.num}}张</span></span>
  194. </template>
  195. <template v-if="row.status == '4'">
  196. <i class="el-icon-success" style="font-size: 16px; color: #19be6b;"></i>
  197. <span>下载完成</span>
  198. </template>
  199. <template v-if="row.status == '5'">
  200. <i class="el-icon-error" style="font-size: 16px; color: #ed4014;"></i>
  201. <span>网络异常,请重试!</span>
  202. </template>
  203. <template v-if="row.status == '6'">
  204. <i class="el-icon-error" style="font-size: 16px; color: #ed4014;"></i>
  205. <span>验证码拦截,请手动验证!</span>
  206. </template>
  207. </template>
  208. </vxe-column>
  209. <vxe-column title="操作" width="80">
  210. <template #default="{ row, rowIndex }">
  211. <i class="el-icon-delete cur-pointer" style="font-size: 20px;" @click="delFile(rowIndex)"></i>
  212. </template>
  213. </vxe-column>
  214. </vxe-table>
  215. </div>
  216. </div>
  217. </template>
  218. <el-dialog title="添加链接" :visible.sync="addVisible" width="500px" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
  219. <div>
  220. <el-form label-position="right" label-width="80px" :rules="rules" :model="formData" ref="formData">
  221. <el-form-item label="目录名称" prop="title">
  222. <el-input v-model="formData.title" placeholder="为空则默认使用网页标题前50个字符"></el-input>
  223. </el-form-item>
  224. <el-form-item label="网页链接" prop="url">
  225. <el-input type="textarea" :rows="10" v-if="menuIndex < 10" :placeholder="'请输入网址链接(例:' + exampleUrl[menuIndex-1] + ')'" v-model="formData.url"></el-input>
  226. <el-input type="textarea" :rows="10" v-else :placeholder="'请输入网址链接'" v-model="formData.url"></el-input>
  227. </el-form-item>
  228. </el-form>
  229. </div>
  230. <span slot="footer" class="dialog-footer">
  231. <el-button @click="addVisible = false; $refs['formData'].resetFields();">取 消</el-button>
  232. <el-button type="primary" @click="onSubmit">确 定</el-button>
  233. </span>
  234. </el-dialog>
  235. <el-dialog title="提示" :visible.sync="loginVisible" width="400px" :close-on-click-modal="false" :close-on-press-escape="false">
  236. <div style="text-align: center; color: #999; font-size: 14px;">
  237. <template v-if="menuIndex == '2'">
  238. <p>京东渠道需要登录后才能下载</p>
  239. <p class="visible-tips-style">目前检测还未登录京东账号,需立即登录</p>
  240. </template>
  241. <template v-else-if="menuIndex == '5'">
  242. <p>小红书渠道需要登录后才能下载</p>
  243. <p class="visible-tips-style">目前检测还未登录小红书账号,需立即登录</p>
  244. </template>
  245. <template v-else>
  246. <p>天猫/淘宝渠道需要登录后才能下载</p>
  247. <p class="visible-tips-style">目前检测还未登录天猫/淘宝账号,需立即登录</p>
  248. </template>
  249. </div>
  250. <div slot="footer" class="dialog-footer-center">
  251. <el-button v-if="menuIndex == '2'" @click="loginVisible = false; loginUrl('https://passport.jd.com/new/login.aspx')">点击登录京东账号</el-button>
  252. <el-button v-else-if="menuIndex == '5'" @click="loginVisible = false; loginUrl('https://www.xiaohongshu.com')">点击登录小红书账号</el-button>
  253. <el-button v-else @click="loginVisible = false; loginUrl('https://login.taobao.com')">点击登录天猫/淘宝账号</el-button>
  254. </div>
  255. </el-dialog>
  256. <!-- 非会员转换提示框 -->
  257. <el-dialog title="非会员提示" :visible.sync="tipsModal" width="400px">
  258. <div class="member-model">
  259. <div class="tips-flex">
  260. <i class="el-icon-s-opportunity"></i>
  261. <p class="m-title">{{tipsDesc}}</p>
  262. </div>
  263. <div class="member-btn">
  264. <el-button size="small" round type="primary" @click="openVip()">开通会员</el-button>
  265. </div>
  266. </div>
  267. </el-dialog>
  268. </el-main>
  269. <el-footer height="48px">
  270. <!-- 更新 -->
  271. <soft-update ref="updateRef" :showDowload="dowloadModel" :dowloadFinish="finishModel"></soft-update>
  272. </el-footer>
  273. </el-container>
  274. </el-container>
  275. </div>
  276. </template>
  277. <script>
  278. import os from 'os'
  279. import fs from 'fs'
  280. import request from 'request'
  281. import path from 'path';
  282. import xlsx from 'node-xlsx';
  283. import softUpdate from './update.vue';
  284. import softHeader from './header.vue';
  285. import softImg from './img.vue';
  286. import electronApi from '@/utils/electronApi';
  287. import pjson from '/package.json'
  288. // import puppeteer from 'puppeteer'
  289. import puppeteer from 'puppeteer-extra'
  290. const StealthPlugin = require('puppeteer-extra-plugin-stealth');
  291. let separator = '';
  292. if (os.platform == 'linux') {
  293. separator = '/'
  294. } else {
  295. separator = '\\'
  296. }
  297. // let chromePath = process.cwd() + '\\resources\\app\\node_modules\\puppeteer\\.local-chromium\\win64-1045629\\chrome-win\\chrome.exe';
  298. // if (process.env.NODE_ENV == 'development') {
  299. // chromePath = process.cwd() + '\\node_modules\\puppeteer\\.local-chromium\\win64-1045629\\chrome-win\\chrome.exe';
  300. // }
  301. export default {
  302. name: 'landing-page',
  303. components: {
  304. softUpdate,
  305. softHeader,
  306. softImg
  307. },
  308. data() {
  309. return {
  310. tipsModal: false,
  311. tipsDesc: "非VIP用户不能下载视频,如需完整功能请开通VIP会员。",
  312. settingArr: ['mainImg'],
  313. loginVisible: false,
  314. addVisible: false,
  315. formData: {
  316. title: '',
  317. url: ''
  318. },
  319. rules:{ //
  320. title: [
  321. { min: 0, max: 50, message: '不能超过 50 个字符', trigger: 'blur' },
  322. { pattern: /^[^\\/:*?"<>|]*$/, message: '标题中不能包含 \\ / : * ? " < > |', trigger: 'blur' }
  323. ],
  324. url: [
  325. { required: true, message: '请输入网页链接', trigger: 'blur' },
  326. { pattern: /^(http|https):\/\/.+$/, message: '请输入正确的网址链接(http://或https://开头)', trigger: 'blur' }
  327. ],
  328. },
  329. tabLoading: false,
  330. alibabaList: [],
  331. jdList: [],
  332. tbList: [],
  333. tmallList: [],
  334. redList: [],
  335. commonList: [],
  336. productName: pjson.softInfo.softName,
  337. imgUrl: this.$api.imgUrl,
  338. imgSrc: '',
  339. menuIndex: '3',
  340. settingData: {
  341. detailImg: true,
  342. skuImg: true,
  343. commentImg: false,
  344. video: false,
  345. },
  346. exampleUrl: ['https://www.1688.com', 'https://www.jd.com', 'https://www.tmall.com', 'https://www.taobao.com', 'https://www.xiaohongshu.com'],
  347. fileList: [],
  348. downloadDir: os.userInfo().homedir + separator + "Downloads",
  349. dowloadModel: false,
  350. finishModel: false,
  351. loading: false,
  352. checkLoading: false, //点击检测登录状态
  353. tbStatus: 1, // 1、未检测 2、已经登录 3、未登录
  354. jdStatus: 1, // 1、未检测 2、已经登录 3、未登录
  355. redStatus: 1, //同上
  356. alibabaStatus: 1, //同上上
  357. execLimit: 2,
  358. execNum: 3, // 限制5张
  359. /** 浏览器名称 **/
  360. alibabaBrowser: null,
  361. tbBrowser: null,
  362. jdBrowser: null,
  363. otherBrowser: null,
  364. redBrowser: null,
  365. loginBrowser: null, // 登录用的浏览器实例
  366. };
  367. },
  368. computed: {
  369. listStr: function(){
  370. let str = 'alibaba';
  371. switch(this.menuIndex){
  372. case '1':
  373. str = 'alibaba';
  374. break;
  375. case '2':
  376. str = 'jd';
  377. break;
  378. case '3':
  379. str = 'tmall';
  380. break;
  381. case '4':
  382. str = 'tb';
  383. break;
  384. case '5':
  385. str = 'red';
  386. break;
  387. case '10':
  388. str = 'common';
  389. break;
  390. }
  391. return str;
  392. }
  393. },
  394. async mounted() {
  395. this.$refs.updateRef.updateSoft(true);
  396. let homedir = os.userInfo().homedir;
  397. if (fs.existsSync(homedir + separator + "Desktop")) {
  398. this.downloadDir = homedir + separator + "Desktop"
  399. } else {
  400. this.downloadDir = homedir + separator + "Downloads"
  401. }
  402. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture')) {
  403. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture');
  404. }
  405. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture-jd')) {
  406. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture-jd');
  407. }
  408. let path = os.tmpdir() + separator + 'chrome-data-capture';
  409. let path2 = os.tmpdir() + separator + 'chrome-data-capture-jd';
  410. //this.deleteAll(path, false);
  411. //this.deleteAll(path2, false);
  412. // 打开浏览器
  413. const {
  414. shell
  415. } = require('electron');
  416. const links = document.querySelectorAll('a[href]');
  417. links.forEach(link => {
  418. link.addEventListener('click', e => {
  419. const url = link.getAttribute('href');
  420. e.preventDefault();
  421. shell.openExternal(url);
  422. });
  423. });
  424. // 判断系统版本 低于10使用兼容模式
  425. if(os.release() && os.release().indexOf('.') > -1){
  426. let systemVersion = Number(os.release().split('.')[0]);
  427. if(systemVersion < 10){ // 低于win10,软件打开时候浏览器设置为兼容版
  428. this.$utils.setStorage('versionType', 1);
  429. }else{
  430. this.$utils.setStorage('versionType', 2);
  431. }
  432. }
  433. },
  434. methods: {
  435. // 实时获取浏览器路径
  436. initPath(){
  437. let chromePath = puppeteer.executablePath().replace('win32-1', 'win64-1');
  438. let versionType = this.$utils.getStorage('versionType');
  439. if(versionType && versionType == 1){
  440. chromePath = chromePath.replace('chrome-win', 'chrome7');
  441. }
  442. return chromePath;
  443. },
  444. // 删除文件夹内容
  445. deleteAll(folderPath, flag) {
  446. if (fs.existsSync(folderPath)) {
  447. fs.readdirSync(folderPath).forEach((file, index) => {
  448. var curPath = path.join(folderPath, file);
  449. if (fs.lstatSync(curPath).isDirectory()) {
  450. this.deleteAll(curPath, true);
  451. } else {
  452. fs.unlinkSync(curPath);
  453. }
  454. });
  455. if(flag){
  456. fs.rmdirSync(folderPath);
  457. }
  458. }
  459. },
  460. checkAuthority(){
  461. let authority = this.$refs.headerRef.authority;
  462. this.$refs.imgRef.authority = authority;
  463. },
  464. setMenuIndex(index){
  465. this.menuIndex = index;
  466. if(index.indexOf('2-') > -1){
  467. let num = Number(index.split('-')[1]);
  468. this.$refs.imgRef.setMenuIndex(num);
  469. }
  470. },
  471. // 选择目录
  472. pickPath() {
  473. this.$refs['upload-input'].blur();
  474. electronApi.call('pickDir', []).then((path) => {
  475. if (path) {
  476. this.downloadDir = path;
  477. }
  478. });
  479. },
  480. // 打开自定义下载目录
  481. openFolder() {
  482. let path = this.downloadDir;
  483. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName)) {
  484. path = this.downloadDir + separator + pjson.softInfo.softName;
  485. } else {
  486. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName);
  487. path = this.downloadDir + separator + pjson.softInfo.softName;
  488. }
  489. electronApi.call('showItemInfolder', [path + '\\tty.tty'])
  490. },
  491. openVip() {
  492. this.$refs.headerRef.openVip();
  493. },
  494. updateSoft() {
  495. this.$refs.updateRef.updateSoft();
  496. },
  497. // 删除文件
  498. delFile(rowIndex){
  499. let type = 'alibaba';
  500. switch(this.menuIndex){
  501. case '1':
  502. type = 'alibaba';
  503. break;
  504. case '2':
  505. type = 'jd';
  506. break;
  507. case '3':
  508. type = 'tmall';
  509. break;
  510. case '4':
  511. type = 'tb';
  512. break;
  513. case '5':
  514. type = 'red';
  515. break;
  516. case '10':
  517. type = 'common';
  518. break;
  519. }
  520. this.$confirm('确认删除此行数据吗?', '提示', {
  521. confirmButtonText: '确定',
  522. cancelButtonText: '取消',
  523. type: 'warning'
  524. }).then(() => {
  525. this[type+'List'].splice(rowIndex, 1);
  526. if(this[type+'List'].length == 0) {
  527. this.clearList();
  528. }
  529. }).catch(() => {
  530. });
  531. },
  532. // 清除列表
  533. clearList() {
  534. switch(this.menuIndex){
  535. case '1':
  536. this.alibabaList = [];
  537. break;
  538. case '2':
  539. this.jdList = [];
  540. break;
  541. case '3':
  542. this.tmallList = [];
  543. break;
  544. case '4':
  545. this.tbList = [];
  546. break;
  547. case '5':
  548. this.redList = [];
  549. break;
  550. case '10':
  551. this.commonList = [];
  552. break;
  553. }
  554. },
  555. // 提交表单
  556. onSubmit(){
  557. this.$refs['formData'].validate((valid) => {
  558. if (valid) {
  559. let info = {
  560. url: this.formData.url,
  561. title: this.formData.title,
  562. status: '1',
  563. num: 0,
  564. newPath: ''
  565. }
  566. switch(this.menuIndex){
  567. case '1':
  568. this.alibabaList.push(info);
  569. break;
  570. case '2':
  571. this.jdList.push(info);
  572. break;
  573. case '3':
  574. this.tmallList.push(info);
  575. break;
  576. case '4':
  577. this.tbList.push(info);
  578. break;
  579. case '5':
  580. this.redList.push(info);
  581. break;
  582. case '10':
  583. this.commonList.push(info);
  584. break;
  585. }
  586. this.$refs['formData'].resetFields();
  587. this.addVisible = false;
  588. } else {
  589. return false;
  590. }
  591. });
  592. },
  593. async pickLink() { // 导入链接
  594. const spinLoad = this.$loading();
  595. await electronApi.call('pickFile', ['xlsx,xls', true]).then(async (path) => {
  596. spinLoad.close();
  597. if(path.length > 0){
  598. this.$notify({
  599. title: '提示',
  600. message: '导入成功',
  601. type: 'success'
  602. });
  603. let workSheetsFromBuffer = xlsx.parse(fs.readFileSync(path[0]));
  604. if(workSheetsFromBuffer && workSheetsFromBuffer.length > 0){
  605. let errMsg = "";
  606. workSheetsFromBuffer[0].data.map((item, index) => {
  607. if(item.length > 0){
  608. let ititle = '';
  609. if(item[0]){
  610. ititle = item[0].toString().trim();
  611. }
  612. if(item[0] == '目录名称' && item[1] == '网页链接'){
  613. return false;
  614. }
  615. if(this.containsAnyChar(ititle.toString(), ['\\', '/', ':', '*', '?', '"', '<', '>', '|'])){ //判断是否含有特殊字符
  616. errMsg += "第" + (index+1) + '行-名称不能包以下字符 \\ / : * ? " < > |';
  617. ititle = ititle.replace(/[\\|/|:|*|?|"|<|>||]/g, "");
  618. }
  619. if(item[1] == undefined){
  620. errMsg += "第" + (index+1) + "行格式有误</br>";
  621. item[1] = '';
  622. }else if(!/^(http|https):\/\/.+$/.exec(item[1])){
  623. errMsg += "第" + (index+1) + "行网址格式有误</br>";
  624. }else{
  625. let info = {
  626. url: item[1],
  627. title: ititle,
  628. status: '1',
  629. num: 0,
  630. newPath: ''
  631. }
  632. switch(this.menuIndex){
  633. case '1':
  634. this.alibabaList.push(info);
  635. break;
  636. case '2':
  637. this.jdList.push(info);
  638. break;
  639. case '3':
  640. this.tmallList.push(info);
  641. break;
  642. case '4':
  643. this.tbList.push(info);
  644. break;
  645. case '5':
  646. this.redList.push(info);
  647. break;
  648. case '10':
  649. this.commonList.push(info);
  650. break;
  651. }
  652. }
  653. }
  654. });
  655. if(errMsg){
  656. this.$notify({
  657. title: '请检查文件以下问题',
  658. dangerouslyUseHTMLString: true,
  659. message: errMsg,
  660. type: 'warning'
  661. });
  662. }
  663. }else{
  664. this.$notify({
  665. title: '提示',
  666. message: '格式有误,请重新导入',
  667. type: 'warning'
  668. });
  669. }
  670. }
  671. });
  672. },
  673. settingGroup(name){
  674. let authority = this.$refs.headerRef.authority.isAuthority;
  675. let index = name.indexOf('video');
  676. if(index > -1 && !authority){ // 非会员选中下载视频选项-提示
  677. this.tipsModal = true;
  678. name.splice(index, 1);
  679. }
  680. },
  681. // 清除缓存
  682. async clearCache(){
  683. this.jdStatus = 3;
  684. this.tbStatus = 3;
  685. this.redStatus = 3;
  686. if(this.loginBrowser){
  687. await this.loginBrowser.close();
  688. this.loginBrowser = null;
  689. }
  690. if(this.alibabaBrowser){
  691. await this.alibabaBrowser.close();
  692. this.alibabaBrowser = null;
  693. }
  694. if(this.tbBrowser){
  695. await this.tbBrowser.close();
  696. this.tbBrowser = null;
  697. }
  698. if(this.jdBrowser){
  699. await this.jdBrowser.close();
  700. this.jdBrowser = null;
  701. }
  702. if(this.redBrowser){
  703. await this.redBrowser.close();
  704. this.redBrowser = null;
  705. }
  706. if(this.otherBrowser){
  707. await this.otherBrowser.close();
  708. this.otherBrowser = null;
  709. }
  710. },
  711. // 去登录
  712. loginUrl(url){
  713. (async () => {
  714. try{
  715. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture')) {
  716. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture');
  717. }
  718. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture-jd')) {
  719. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture-jd');
  720. }
  721. let userDataDir = os.tmpdir() + separator + 'chrome-data-capture';
  722. if(url.indexOf('.jd.com/') > -1){
  723. userDataDir = os.tmpdir() + separator + 'chrome-data-capture-jd';
  724. }
  725. this.loginBrowser = await puppeteer.launch({
  726. headless: false,
  727. executablePath: this.initPath(),
  728. args: ['--window-size=1280,800'],
  729. userDataDir: userDataDir,
  730. });
  731. const page = await this.loginBrowser.newPage();
  732. await page.setViewport({ width: 1280, height: 800 });
  733. await page.evaluateOnNewDocument(() => {
  734. const newProto = navigator.__proto__;
  735. delete newProto.webdriver;
  736. navigator.__proto__ = newProto;
  737. });
  738. await page.goto(url, {waitUntil : 'networkidle2'});
  739. }catch(e){
  740. this.showError(e);
  741. }
  742. })();
  743. },
  744. // 开始下载
  745. async exportFile(flag) {
  746. if(this.loginBrowser){
  747. await this.loginBrowser.close();
  748. this.loginBrowser = null;
  749. }
  750. let authority = this.$refs.headerRef.authority.isAuthority;
  751. if (!fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName)) {
  752. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName);
  753. }
  754. let fileList = [];
  755. switch(this.menuIndex){
  756. case '1': // 阿里巴巴
  757. fileList = this.alibabaList;
  758. break;
  759. case '2': // 京东
  760. fileList = this.jdList;
  761. break;
  762. case '3': // 天猫
  763. fileList = this.tmallList;
  764. break;
  765. case '4': // 淘宝
  766. fileList = this.tbList;
  767. break;
  768. case '5': // 小红书
  769. fileList = this.redList;
  770. break;
  771. case '10': // 普通网址
  772. fileList = this.commonList;
  773. break;
  774. }
  775. if(fileList.length > 0){
  776. if (!authority && !flag) { // 非会员点击转换弹出提示框
  777. this.$refs.headerRef.memberModel = true;
  778. this.$refs.headerRef.isClick = true;
  779. return false;
  780. } else {
  781. this.$refs.headerRef.memberModel = false;
  782. this.loading = true;
  783. setTimeout(() => {
  784. this.loading = false;
  785. }, 60000);
  786. }
  787. if (!fs.existsSync(os.tmpdir() + separator + 'chrome-data-capture')) {
  788. fs.mkdirSync(os.tmpdir() + separator + 'chrome-data-capture');
  789. }
  790. if(this.menuIndex == '2'){ // 京东
  791. if(this.jdStatus == 1 || this.jdStatus == 3){ // 未检测登录状态/或提示未登录状态,开始检测
  792. await this.checkJdLogin().then((data) => {
  793. if(data != 2){ // 未登录
  794. this.jdStatus = 3;
  795. this.loginVisible = true;
  796. }
  797. }).catch(err => {
  798. this.jdStatus = 3;
  799. });
  800. if(this.jdStatus == 3){
  801. this.loading = false;
  802. return false;
  803. }
  804. }
  805. }
  806. if(this.menuIndex == '3' || this.menuIndex == '4'){ // 天猫/淘宝
  807. if(this.tbStatus == 1 || this.tbStatus == 3){ // 未检测登录状态/或提示未登录状态,开始检测
  808. await this.checkLogin().then((data) => {
  809. if(data != 2){ // 未登录
  810. this.tbStatus = 3;
  811. this.loginVisible = true;
  812. }
  813. }).catch(err => {
  814. this.tbStatus = 3;
  815. });
  816. }
  817. if(this.tbStatus == 3){
  818. this.loading = false;
  819. return false;
  820. }
  821. }
  822. if(this.menuIndex == '5'){ // 小红书
  823. if(this.redStatus == 1 || this.redStatus == 3){ // 未检测登录状态/或提示未登录状态,开始检测
  824. await this.checkRedLogin().then((data) => {
  825. if(data != 2){ // 未登录
  826. this.redStatus = 3;
  827. this.loginVisible = true;
  828. }
  829. }).catch(err => {
  830. this.redStatus = 3;
  831. });
  832. if(this.redStatus == 3){
  833. this.loading = false;
  834. return false;
  835. }
  836. }
  837. }
  838. let taskArr = [];
  839. let task = "";
  840. let browserName = '';
  841. let userDataDir = os.tmpdir() + separator + 'chrome-data-capture';
  842. switch(this.menuIndex){
  843. case '1': // 阿里巴巴
  844. browserName = 'alibaba';
  845. break;
  846. case '2': // 京东
  847. browserName = 'jd';
  848. userDataDir = os.tmpdir() + separator + 'chrome-data-capture-jd';
  849. break;
  850. case '3': // 天猫
  851. case '4': // 淘宝
  852. browserName = 'tb';
  853. break;
  854. case '5': // 小红书
  855. browserName = 'red';
  856. break;
  857. case '10': // 普通网址
  858. browserName = 'other';
  859. break;
  860. }
  861. // 运行不同平台的浏览器
  862. puppeteer.use(StealthPlugin());
  863. this[browserName + 'Browser'] = await puppeteer.launch({
  864. executablePath: this.initPath(),
  865. userDataDir: userDataDir,
  866. args: [
  867. '--start-maximized',
  868. '--no-sandbox',
  869. '--disable-setuid-sandbox',
  870. '--disable-blink-features=AutomationControlled',
  871. ]
  872. });
  873. for(let i = 0; i < fileList.length; i++){
  874. let item = fileList[i];
  875. switch(this.menuIndex){
  876. case '1': // 阿里巴巴
  877. task = this.alibabaDownload(item, this.alibabaBrowser);
  878. break;
  879. case '2': // 京东
  880. task = this.jdScanImg(item, this.jdBrowser);
  881. break;
  882. case '3': // 天猫
  883. case '4': // 淘宝
  884. task = this.tbScanImg(item, this.tbBrowser);
  885. break;
  886. case '5': // 小红书
  887. task = this.redDownload(item, this.redBrowser);
  888. break;
  889. case '10': // 普通网址
  890. task = this.normalDownload(item, this.otherBrowser);
  891. break;
  892. }
  893. if(task){
  894. taskArr.push(task);
  895. }
  896. let numType = this.$utils.getStorage('numType');
  897. if(numType){
  898. this.execLimit = numType;
  899. }
  900. if((i+1) % this.execLimit == 0){
  901. await Promise.all(taskArr).then(result => {
  902. taskArr = [];
  903. }).catch(err => {
  904. console.log('err'+i, err)
  905. })
  906. }
  907. }
  908. if(taskArr.length > 0){
  909. await Promise.all(taskArr).then(result => {
  910. taskArr = [];
  911. }).catch(err => {
  912. // 错误文件添加到服务中
  913. console.log('err',err)
  914. })
  915. }
  916. if(this[browserName + 'Browser']){
  917. this[browserName + 'Browser'].close();
  918. this[browserName + 'Browser'] = null;
  919. }
  920. this.loading = false;
  921. // 打开文件夹
  922. if(fileList.length > 0){
  923. this.$message({message: '恭喜你,任务已完成!', type: 'success'});
  924. electronApi.call('showItemInfolder',[this.downloadDir + separator + pjson.softInfo.softName +'\\tty.tty'])
  925. }
  926. }
  927. },
  928. // 10-普通网址下载
  929. async normalDownload(urlInfo, otherBrowser){
  930. let task = await new Promise((resolve,reject) =>{
  931. (async () => {
  932. try{
  933. let authority = this.$refs.headerRef.authority.isAuthority;
  934. let number = 0;
  935. urlInfo.status = '2';
  936. urlInfo.num = 0;
  937. // puppeteer.use(StealthPlugin());
  938. // const otherBrowser = await puppeteer.launch({
  939. // executablePath: this.initPath(),
  940. // userDataDir: os.tmpdir() + separator + 'chrome-data-capture',
  941. // args: [
  942. // '--start-maximized',
  943. // '--no-sandbox',
  944. // '--disable-setuid-sandbox',
  945. // '--disable-blink-features=AutomationControlled',
  946. // ]
  947. // });
  948. const page = await otherBrowser.newPage();
  949. page.on('response', async(response) => {
  950. // 检查响应的 MIME 类型是否以 'image/' 开头
  951. if (response.headers()['content-type'] && response.headers()['content-type'].startsWith('image/') && response.headers()['content-length']) {
  952. let imgArr = ['gif', 'jpeg', 'png', 'webp', 'svg', 'tiff', 'bmp', 'ico', 'avif'];
  953. let imgType = 'jpg';
  954. let isBase = false;
  955. imgArr.map((item, index) => {
  956. if(response.headers()['content-type'].indexOf(item) > -1){
  957. imgType = item;
  958. if(item == 'jpeg'){
  959. imgType = 'jpg';
  960. }else if(item == 'avif'){
  961. imgType = 'png';
  962. }
  963. }
  964. });
  965. let url = response.url();
  966. let regex = /^data:image\/[\w|+|-]+;base64,/;
  967. if(regex.exec(url)){
  968. url = response.url().replace(/^data:image\/[\w|+|-]+;base64,/, '');
  969. isBase = true;
  970. }
  971. let imgInfo = {
  972. url: url,
  973. contentType: response.headers()['content-type'],
  974. status: response.status(),
  975. imgType: imgType,
  976. isBase: isBase
  977. }
  978. if(urlInfo.title){
  979. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title)) {
  980. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  981. } else {
  982. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title);
  983. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  984. }
  985. }else{
  986. await this.getTitle(page, urlInfo); // 生成页面标题对应的文件夹
  987. }
  988. let outputPath = urlInfo.newPath + '\\' + this.randomString(35) + '.' + imgInfo.imgType;
  989. urlInfo.status = '3';
  990. number++;
  991. if(!authority && number <= this.execNum){
  992. if(imgInfo.isBase){ //base64位图片下载
  993. this.downloadBaseImage(imgInfo.url, outputPath, urlInfo)
  994. }else{
  995. this.downloadImage(imgInfo.url, outputPath, urlInfo);
  996. }
  997. }
  998. if(authority){
  999. if(imgInfo.isBase){ //base64位图片下载
  1000. this.downloadBaseImage(imgInfo.url, outputPath, urlInfo)
  1001. }else{
  1002. this.downloadImage(imgInfo.url, outputPath, urlInfo);
  1003. }
  1004. }
  1005. }
  1006. });
  1007. await page.goto(urlInfo.url, {waitUntil : 'networkidle2'});
  1008. let pageInfo = await page.evaluate(() => {
  1009. let cHeight = document.documentElement.clientHeight;
  1010. let scrollHeight = document.body.scrollHeight;
  1011. return {'scrollHeight': scrollHeight, 'cHeight': cHeight}
  1012. });
  1013. let scrollHeight = pageInfo.scrollHeight;
  1014. let cHeight = pageInfo.cHeight;
  1015. let num = Math.ceil(scrollHeight / cHeight);
  1016. let start = -1;
  1017. let scrollInt = setInterval(async() => {
  1018. start ++;
  1019. await page.evaluate((start) => {
  1020. let cHeight = document.documentElement.clientHeight;
  1021. window.scrollTo({
  1022. top: cHeight * start,
  1023. behavior: "smooth"
  1024. });
  1025. }, start);
  1026. if(start > num || start > 200){ // 防止页面过长,滚动200次自动停止
  1027. clearInterval(scrollInt);
  1028. await page.close();
  1029. urlInfo.status = '4';
  1030. resolve(true);
  1031. this.loading = false;
  1032. }
  1033. }, 300);
  1034. }catch(e){
  1035. urlInfo.status = '5';
  1036. reject(e);
  1037. this.showError(e);
  1038. }
  1039. })();
  1040. });
  1041. },
  1042. // 检查天猫淘宝登录状态
  1043. checkAlibabaLogin(){
  1044. this.checkLoading = true;
  1045. this.alibabaStatus = 1;
  1046. return new Promise((resolve, reject) => {
  1047. (async () => {
  1048. try{
  1049. if(this.loginBrowser){
  1050. await this.loginBrowser.close();
  1051. this.loginBrowser = null;
  1052. }
  1053. this.loginBrowser = await puppeteer.launch({
  1054. executablePath: this.initPath(),
  1055. args: ['--window-size=1280,800'],
  1056. userDataDir: os.tmpdir() + separator + 'chrome-data-capture',
  1057. });
  1058. const page = await this.loginBrowser.newPage();
  1059. await page.setViewport({ width: 1280, height: 800 });
  1060. let testUrl = 'https://www.1688.com';
  1061. await page.goto(testUrl, {waitUntil : 'networkidle2'});
  1062. let loginInfo = await page.evaluate(() => {
  1063. let navTags = document.querySelector('.site-nav-sign a');
  1064. let userTags = document.querySelector('.site-nav-user a');
  1065. if(navTags && navTags.innerHTML.indexOf('登录') > -1){
  1066. return false;
  1067. }else if(userTags){
  1068. return true;
  1069. }else{
  1070. return false;
  1071. }
  1072. });
  1073. if(loginInfo){
  1074. this.tbStatus = 2;
  1075. }else{
  1076. this.tbStatus = 3;
  1077. }
  1078. resolve(this.tbStatus);
  1079. this.checkLoading = false;
  1080. await this.loginBrowser.close();
  1081. this.loginBrowser = null;
  1082. }catch(e){
  1083. this.checkLoading = false;
  1084. reject(3);
  1085. this.showError(e);
  1086. }
  1087. })();
  1088. });
  1089. },
  1090. // 1 - 阿里巴巴下载
  1091. async alibabaDownload(urlInfo, browser){
  1092. let task = await new Promise((resolve,reject) =>{
  1093. (async () => {
  1094. try{
  1095. let authority = this.$refs.headerRef.authority.isAuthority;
  1096. urlInfo.status = '2';
  1097. urlInfo.num = 0;
  1098. // puppeteer.use(StealthPlugin());
  1099. // const browser = await puppeteer.launch({
  1100. // executablePath: this.initPath(),
  1101. // userDataDir: os.tmpdir() + separator + 'chrome-data-capture',
  1102. // });
  1103. const page = await browser.newPage();
  1104. await page.goto(urlInfo.url, {waitUntil : 'networkidle2'});
  1105. if(urlInfo.title){
  1106. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title)) {
  1107. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  1108. } else {
  1109. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title);
  1110. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  1111. }
  1112. }else{
  1113. await this.getTitle(page, urlInfo); // 生成页面标题对应的文件夹
  1114. }
  1115. let pageInfo = await page.evaluate(() => {
  1116. let cHeight = document.documentElement.clientHeight;
  1117. let scrollHeight = document.body.scrollHeight;
  1118. return {'scrollHeight': scrollHeight, 'cHeight': cHeight}
  1119. });
  1120. let scrollHeight = pageInfo.scrollHeight;
  1121. let cHeight = pageInfo.cHeight;
  1122. let num = Math.ceil(scrollHeight / cHeight);
  1123. let start = -1;
  1124. let scrollInt = setInterval(async() => {
  1125. start ++;
  1126. if(this.settingArr.indexOf('detailImg') > -1){
  1127. let scrollHeight2 = await page.evaluate((start) => {
  1128. let scrollHeight = document.body.scrollHeight;
  1129. let cHeight = document.documentElement.clientHeight;
  1130. // let num = Math.ceil(scrollHeight / cHeight);
  1131. window.scrollTo({
  1132. top: cHeight * start,
  1133. behavior: "smooth"
  1134. });
  1135. return scrollHeight;
  1136. }, start);
  1137. num = Math.ceil(scrollHeight2 / cHeight);
  1138. }
  1139. if(start > num || start > 200){ // 防止页面过长,滚动200次自动停止
  1140. urlInfo.status = '3';
  1141. clearInterval(scrollInt);
  1142. await this.alibabaScanImg(browser, page, urlInfo, authority);
  1143. this.loading = false;
  1144. urlInfo.status = '4';
  1145. resolve(true);
  1146. }
  1147. }, 300);
  1148. }catch(e){
  1149. urlInfo.status = '5';
  1150. reject(e);
  1151. this.showError(e);
  1152. }
  1153. })();
  1154. });
  1155. },
  1156. // 检查京东登录状态
  1157. checkJdLogin(){
  1158. this.checkLoading = true;
  1159. this.jdStatus = 1;
  1160. return new Promise((resolve, reject) => {
  1161. (async () => {
  1162. try{
  1163. if(this.loginBrowser){
  1164. await this.loginBrowser.close();
  1165. this.loginBrowser = null;
  1166. }
  1167. this.loginBrowser = await puppeteer.launch({
  1168. executablePath: this.initPath(),
  1169. args: ['--window-size=1280,800'],
  1170. userDataDir: os.tmpdir() + separator + 'chrome-data-capture-jd',
  1171. });
  1172. const page = await this.loginBrowser.newPage();
  1173. await page.setViewport({ width: 1280, height: 800 });
  1174. let testUrl = 'https://www.jd.com';
  1175. await page.goto(testUrl, {waitUntil : 'networkidle2'});
  1176. let loginInfo = await page.evaluate(() => {
  1177. let navTags = document.querySelector('.link-login');
  1178. let userTags = document.querySelector('.nickname');
  1179. let userTags2 = document.querySelector('.nick'); //企业账号
  1180. if(navTags && navTags.innerHTML.indexOf('请登录') > -1){
  1181. return false;
  1182. }else if(userTags || userTags2){
  1183. return true;
  1184. }else{
  1185. return false;
  1186. }
  1187. });
  1188. if(loginInfo){
  1189. this.jdStatus = 2;
  1190. }else{
  1191. this.jdStatus = 3;
  1192. }
  1193. resolve(this.jdStatus);
  1194. this.checkLoading = false;
  1195. await this.loginBrowser.close();
  1196. this.loginBrowser = null;
  1197. }catch(e){
  1198. this.checkLoading = false;
  1199. reject(3);
  1200. this.showError(e);
  1201. }
  1202. })();
  1203. });
  1204. },
  1205. // 京东下载
  1206. async jdScanImg(urlInfo, jdBrowser){
  1207. let task = await new Promise((resolve,reject) =>{
  1208. (async () => {
  1209. try{
  1210. let authority = this.$refs.headerRef.authority.isAuthority;
  1211. urlInfo.status = '2';
  1212. urlInfo.num = 0;
  1213. // puppeteer.use(StealthPlugin());
  1214. // const jdBrowser = await puppeteer.launch({
  1215. // executablePath: this.initPath(),
  1216. // userDataDir: os.tmpdir() + separator + 'chrome-data-capture-jd',
  1217. // args: [
  1218. // '--no-sandbox',
  1219. // '--disable-setuid-sandbox',
  1220. // '--disable-blink-features=AutomationControlled',
  1221. // ]
  1222. // });
  1223. const page = await jdBrowser.newPage();
  1224. await page.evaluateOnNewDocument(() => {
  1225. const newProto = navigator.__proto__;
  1226. delete newProto.webdriver;
  1227. navigator.__proto__ = newProto;
  1228. });
  1229. let jdImgInfo = {
  1230. mainImg: [],
  1231. skuImg: [],
  1232. commentImg: [],
  1233. detailImg: [],
  1234. video: []
  1235. };
  1236. let titleFlag = true;
  1237. let skuNum = 0, mainNum = 0, videoNum = 0;
  1238. page.on('response', async(response) => {
  1239. if(titleFlag){ // 第一次生成页面标题
  1240. if(urlInfo.title){
  1241. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title)) {
  1242. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  1243. } else {
  1244. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title);
  1245. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  1246. }
  1247. }else{
  1248. await this.getTitle(page, urlInfo); // 生成页面标题对应的文件夹
  1249. }
  1250. titleFlag = false;
  1251. }
  1252. // 检查响应的 MIME 类型是否以 'image/' 开头
  1253. if (response.headers()['content-type'] && response.headers()['content-type'].startsWith('application/json')) {
  1254. let detailUrl = '';
  1255. let videoUrl = '';
  1256. if(response.url().indexOf('/description/') > -1){ // 商品详情接口
  1257. detailUrl = response.url();
  1258. }
  1259. if(response.url().indexOf('/tencent/video_v3') > -1){ // 商品视频接口
  1260. videoUrl = response.url();
  1261. }
  1262. urlInfo.status = '3';
  1263. if(detailUrl && this.settingArr.indexOf('detailImg') > -1){ // jd商品详情
  1264. try {
  1265. let data = await response.text();
  1266. if(data.indexOf('showdesc(') > -1){
  1267. data = data.slice(0, -1).replace('showdesc(', '');
  1268. }
  1269. let detailInfo = JSON.parse(data);
  1270. let regex = /\/\/img[0-9]+.360buyimg.com\S+.(jpg|jpeg|png|gif|avif|tif|tiff)/g;
  1271. let detailImgArr = [];
  1272. if(detailInfo && detailInfo.content){
  1273. detailImgArr = detailInfo.content.match(regex);
  1274. }
  1275. jdImgInfo.detailImg = detailImgArr;
  1276. for(let i = 0; i < detailImgArr.length; i++){
  1277. let imgUrl = 'https:' + detailImgArr[i];
  1278. imgUrl = imgUrl.replace('.avif', '');
  1279. let fileName = imgUrl.split('/').pop();
  1280. if(fileName){
  1281. let queryIndex = fileName.indexOf('?');
  1282. if (queryIndex !== -1) {
  1283. fileName = fileName.substr(0, queryIndex);
  1284. }
  1285. let num = Number(i) + 1;
  1286. let suffix = '';
  1287. if(fileName.lastIndexOf('.') > -1){
  1288. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1289. }
  1290. if (!fs.existsSync(urlInfo.newPath + '\\详情图')) {
  1291. fs.mkdirSync(urlInfo.newPath + '\\详情图');
  1292. }
  1293. let outputPath = urlInfo.newPath + '\\详情图\\详情图' + num + suffix;
  1294. if(!authority && i < this.execNum){
  1295. await this.downloadImage(imgUrl, outputPath, urlInfo);
  1296. }
  1297. if(authority){
  1298. await this.downloadImage(imgUrl, outputPath, urlInfo);
  1299. }
  1300. }
  1301. }
  1302. } catch (error) {
  1303. console.error('详情图片解析失败:', error);
  1304. }
  1305. }
  1306. if(videoUrl && this.settingArr.indexOf('video') > -1){ // jd商品视频
  1307. try {
  1308. let data = await response.text();
  1309. data = data.slice(0, -1).replace(/^jQuery[0-9]+\(/, '');
  1310. let videoInfo = JSON.parse(data);
  1311. let videoUrl = videoInfo.playUrl;
  1312. jdImgInfo.video.push(videoUrl);
  1313. let fileName = videoUrl.split('/').pop();
  1314. if(fileName){
  1315. let queryIndex = fileName.indexOf('?');
  1316. if (queryIndex !== -1) {
  1317. fileName = fileName.substr(0, queryIndex);
  1318. }
  1319. if (!fs.existsSync(urlInfo.newPath + '\\视频')) {
  1320. fs.mkdirSync(urlInfo.newPath + '\\视频');
  1321. }
  1322. let outputPath = urlInfo.newPath + '\\视频\\' + fileName;
  1323. await this.downloadImage(videoUrl, outputPath, urlInfo);
  1324. }
  1325. } catch (error) {
  1326. console.error('视频解析失败:', error);
  1327. }
  1328. }
  1329. }
  1330. });
  1331. await page.goto(urlInfo.url, {waitUntil : 'networkidle2'});
  1332. /**new**/
  1333. //detailImg:详情图;skuImg:sku图片;commentImg: 评论图;video: 视频
  1334. const imgInfo = await page.evaluate((authority, execNum) => {
  1335. let outObj = {
  1336. mainImg: [],
  1337. skuImg: [],
  1338. };
  1339. //主图
  1340. let arr1 = document.querySelectorAll('#spec-list img');
  1341. for(let i=0; i< arr1.length; i++){
  1342. let mainImgUrl = arr1[i].src;
  1343. let reg = /\/n[0-9]+\/jfs\//;
  1344. let reg2 = /\/n[0-9]+\/s(54|50)x(54|66)_jfs\//;
  1345. let reg3 = /![a-z]+_[0-9]+x[0-9]+(.avif)?/;
  1346. let replaceStr = '/n1/s800x800_jfs/';
  1347. if(mainImgUrl.match(/\/n[0-9]+\/s50x66_jfs\//)){
  1348. replaceStr = '/n1/s750x1000_jfs/';
  1349. }
  1350. mainImgUrl = mainImgUrl.replace(reg, replaceStr).replace(reg2, replaceStr).replace(reg3, '').replace('.avif', '');
  1351. if(!authority && i < execNum){
  1352. outObj.mainImg.push(mainImgUrl);
  1353. }
  1354. if(authority){
  1355. outObj.mainImg.push(mainImgUrl);
  1356. }
  1357. }
  1358. //sku图片
  1359. let arr2 = document.querySelectorAll('#choose-attr-1 img');
  1360. for(let i=0; i< arr2.length; i++){
  1361. let skuImgUrl = arr2[i].src;
  1362. let skuReg = /\/n[0-9]+\/s(60|40)x(80|40)_jfs\//;
  1363. let replaceStr = '/n1/s800x800_jfs/';
  1364. if(skuImgUrl.match(/\/n[0-9]+\/s60x80_jfs\//)){
  1365. replaceStr = '/n1/s750x1000_jfs/';
  1366. }
  1367. skuImgUrl = skuImgUrl.replace(skuReg, replaceStr).replace('.avif', '');
  1368. if(!authority && i < execNum){
  1369. outObj.skuImg.push(skuImgUrl);
  1370. }
  1371. if(authority){
  1372. outObj.skuImg.push(skuImgUrl);
  1373. }
  1374. }
  1375. return outObj;
  1376. }, authority, this.execNum);
  1377. if(this.settingArr.indexOf('mainImg') > -1){
  1378. // 主图下载
  1379. for(let j = 0; j < imgInfo.mainImg.length; j++){
  1380. let fileName = imgInfo.mainImg[j].split('/').pop();
  1381. if(fileName){
  1382. let queryIndex = fileName.indexOf('?');
  1383. if (queryIndex !== -1) {
  1384. fileName = fileName.substr(0, queryIndex);
  1385. }
  1386. let num = Number(j) + 1;
  1387. let suffix = '';
  1388. if(fileName.lastIndexOf('.') > -1){
  1389. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1390. }
  1391. if (!fs.existsSync(urlInfo.newPath + '\\主图')) {
  1392. fs.mkdirSync(urlInfo.newPath + '\\主图');
  1393. }
  1394. let outputPath = urlInfo.newPath + '\\主图\\主图' + num + suffix;
  1395. await this.downloadImage(imgInfo.mainImg[j], outputPath, urlInfo);
  1396. }
  1397. }
  1398. }
  1399. // sku图片下载
  1400. if(this.settingArr.indexOf('skuImg') > -1){
  1401. for(let j = 0; j < imgInfo.skuImg.length; j++){
  1402. let fileName = imgInfo.skuImg[j].split('/').pop();
  1403. if(fileName){
  1404. let queryIndex = fileName.indexOf('?');
  1405. if (queryIndex !== -1) {
  1406. fileName = fileName.substr(0, queryIndex);
  1407. }
  1408. let num = Number(j) + 1;
  1409. let suffix = '';
  1410. if(fileName.lastIndexOf('.') > -1){
  1411. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1412. }
  1413. if (!fs.existsSync(urlInfo.newPath + '\\sku图')) {
  1414. fs.mkdirSync(urlInfo.newPath + '\\sku图');
  1415. }
  1416. let outputPath = urlInfo.newPath + '\\sku图\\sku图' + num + suffix;
  1417. await this.downloadImage(imgInfo.skuImg[j], outputPath, urlInfo);
  1418. }
  1419. }
  1420. }
  1421. /**end**/
  1422. await page.close();
  1423. urlInfo.status = '4';
  1424. resolve(true);
  1425. }catch(e){
  1426. reject(e);
  1427. this.showError(e);
  1428. }
  1429. })();
  1430. });
  1431. },
  1432. // 检查天猫淘宝登录状态
  1433. checkLogin(){
  1434. this.checkLoading = true;
  1435. this.tbStatus = 1;
  1436. return new Promise((resolve, reject) => {
  1437. (async () => {
  1438. try{
  1439. if(this.loginBrowser){
  1440. await this.loginBrowser.close();
  1441. this.loginBrowser = null;
  1442. }
  1443. this.loginBrowser = await puppeteer.launch({
  1444. executablePath: this.initPath(),
  1445. args: ['--window-size=1280,800'],
  1446. userDataDir: os.tmpdir() + separator + 'chrome-data-capture',
  1447. });
  1448. const page = await this.loginBrowser.newPage();
  1449. await page.setViewport({ width: 1280, height: 800 });
  1450. let testUrl = 'https://www.taobao.com';
  1451. await page.goto(testUrl, {waitUntil : 'networkidle2'});
  1452. let loginInfo = await page.evaluate(() => {
  1453. let navTags = document.querySelector('.site-nav-sign a');
  1454. let userTags = document.querySelector('.site-nav-user a');
  1455. if(navTags && navTags.innerHTML.indexOf('登录') > -1){
  1456. return false;
  1457. }else if(userTags){
  1458. return true;
  1459. }else{
  1460. return false;
  1461. }
  1462. });
  1463. if(loginInfo){
  1464. this.tbStatus = 2;
  1465. }else{
  1466. this.tbStatus = 3;
  1467. }
  1468. resolve(this.tbStatus);
  1469. this.checkLoading = false;
  1470. await this.loginBrowser.close();
  1471. this.loginBrowser = null;
  1472. }catch(e){
  1473. this.checkLoading = false;
  1474. reject(3);
  1475. this.showError(e);
  1476. }
  1477. })();
  1478. });
  1479. },
  1480. // 淘宝天猫扫描下载图片
  1481. async tbScanImg(urlInfo, tbBrowser){
  1482. let task = await new Promise((resolve,reject) =>{
  1483. (async () => {
  1484. try{
  1485. let authority = this.$refs.headerRef.authority.isAuthority;
  1486. urlInfo.status = '2';
  1487. urlInfo.num = 0;
  1488. // puppeteer.use(StealthPlugin());
  1489. // const tbBrowser = await puppeteer.launch({
  1490. // executablePath: this.initPath(),
  1491. // userDataDir: os.tmpdir() + separator + 'chrome-data-capture',
  1492. // });
  1493. const page = await tbBrowser.newPage();
  1494. await page.goto(urlInfo.url, {waitUntil : 'networkidle0'});
  1495. if(urlInfo.title){
  1496. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title)) {
  1497. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  1498. } else {
  1499. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title);
  1500. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  1501. }
  1502. }else{
  1503. await this.getTitle(page, urlInfo); // 生成页面标题对应的文件夹
  1504. }
  1505. let responseVideo = [];
  1506. page.on('response', async(response) => {
  1507. // 检查响应的 MIME 类型是否以 'video/' 开头
  1508. if (response.headers()['content-type'] && response.headers()['content-type'].startsWith('video/')) {
  1509. responseVideo.push(response.url());
  1510. }
  1511. });
  1512. if(this.settingArr.indexOf('video') > -1){ // 用户选择下载视频的时候才会触发
  1513. //鼠标放在主图第一张,生成视频
  1514. const elementHandle = await page.$('li[class*=thumbnail--]');
  1515. if(elementHandle){
  1516. const classListProperty = await elementHandle.getProperty('classList');
  1517. const classList = await classListProperty.jsonValue();
  1518. const classesArray = Object.values(classList);
  1519. const allClasses = classesArray.join('.');
  1520. if(allClasses && allClasses.indexOf('active-') < 0){
  1521. await page.hover('li.'+allClasses);
  1522. }
  1523. }
  1524. }
  1525. let iframeElementHandle = await page.$('iframe');
  1526. if(iframeElementHandle){
  1527. let m1 = await page.$('img[class^=PicGallery--thumbnailPic--]');
  1528. let m2 = await page.$('img[class*=thumbnailPic--]');
  1529. if(!m1 && !m2){ // 出现弹窗而且没有主图,判断为拦截模式
  1530. await page.close();
  1531. urlInfo.status = '6';
  1532. resolve(true);
  1533. this.loading = false;
  1534. return false;
  1535. }
  1536. }
  1537. let pageInfo = await page.evaluate(() => {
  1538. let cHeight = document.documentElement.clientHeight;
  1539. let scrollHeight = document.body.scrollHeight;
  1540. return {'scrollHeight': scrollHeight, 'cHeight': cHeight}
  1541. });
  1542. let scrollHeight = pageInfo.scrollHeight;
  1543. let cHeight = pageInfo.cHeight;
  1544. let num = Math.ceil(scrollHeight / cHeight);
  1545. let start = -1;
  1546. let scrollInt = setInterval(async() => {
  1547. start ++;
  1548. if(this.settingArr.indexOf('commentImg') > -1 && this.settingArr.indexOf('detailImg') < 0){ // 选择了评论图没选择详情图
  1549. let scrollHeight2 = await page.evaluate((start) => {
  1550. let scrollHeight = document.body.scrollHeight;
  1551. let cHeight = document.documentElement.clientHeight;
  1552. let obj = document.getElementById('container') || document.getElementById('content');
  1553. if(obj && obj.getBoundingClientRect().top < -500){
  1554. return -1;
  1555. }
  1556. window.scrollTo({
  1557. top: cHeight * start,
  1558. behavior: "smooth"
  1559. });
  1560. return scrollHeight;
  1561. }, start);
  1562. if(scrollHeight2 > 0){
  1563. num = Math.ceil(scrollHeight2 / cHeight);
  1564. }else{
  1565. num = 0;
  1566. }
  1567. }
  1568. if(this.settingArr.indexOf('detailImg') > -1){ // 选择详情图
  1569. let scrollHeight2 = await page.evaluate((start) => {
  1570. let scrollHeight = document.body.scrollHeight;
  1571. let cHeight = document.documentElement.clientHeight;
  1572. let obj = document.getElementById('container') || document.getElementById('content');
  1573. if(obj && obj.getBoundingClientRect().bottom < 100){
  1574. return -1;
  1575. }
  1576. window.scrollTo({
  1577. top: cHeight * start,
  1578. behavior: "smooth"
  1579. });
  1580. return scrollHeight;
  1581. }, start);
  1582. if(scrollHeight2 > 0){
  1583. num = Math.ceil(scrollHeight2 / cHeight);
  1584. }else{
  1585. num = 0;
  1586. }
  1587. }
  1588. if(this.settingArr.indexOf('skuImg') > -1 && this.settingArr.indexOf('commentImg') < 0 && this.settingArr.indexOf('detailImg') < 0){ // 选择了sku图片没选择评论和详情
  1589. let scrollHeight2 = await page.evaluate((start) => {
  1590. let scrollHeight = document.body.scrollHeight;
  1591. let cHeight = document.documentElement.clientHeight;
  1592. let obj = document.querySelector('div[class^=SkuContent--]');
  1593. if(obj && obj.getBoundingClientRect().bottom < 500){
  1594. return -1;
  1595. }
  1596. window.scrollTo({
  1597. top: cHeight * start,
  1598. behavior: "smooth"
  1599. });
  1600. return scrollHeight;
  1601. }, start);
  1602. if(scrollHeight2 > 0){
  1603. num = Math.ceil(scrollHeight2 / cHeight);
  1604. }else{
  1605. num = 0;
  1606. }
  1607. }
  1608. if(this.settingArr.indexOf('skuImg') < 0 && this.settingArr.indexOf('detailImg') < 0 && this.settingArr.indexOf('commentImg') < 0){ // 没有选择评论图和详情图
  1609. num = 0;
  1610. }
  1611. if(start > num || start > 200){ // 防止页面过长,滚动200次自动停止
  1612. urlInfo.status = '3';
  1613. clearInterval(scrollInt);
  1614. //detailImg:详情图;skuImg:sku图片;commentImg: 评论图;video: 视频
  1615. const imgInfo = await page.evaluate((authority, execNum) => {
  1616. let outObj = {
  1617. mainImg: [],
  1618. detailImg: [],
  1619. skuImg: [],
  1620. commentImg: [],
  1621. video: []
  1622. };
  1623. // 正则表达式匹配字符 重写图片路径
  1624. let regex = /\.(.{3,4})_[0-9a-zA-z]+\.(.{3,4})_\.(.{3,4})/;
  1625. //主图
  1626. let arr1 = document.querySelectorAll('img[class^=PicGallery--thumbnailPic--]');
  1627. if(arr1.length === 0){
  1628. arr1 = document.querySelectorAll('img[class*=thumbnailPic--]');
  1629. }
  1630. for(let i=0; i< arr1.length; i++){
  1631. let mainImgUrl = arr1[i].src;
  1632. let result = regex.exec(mainImgUrl);
  1633. if(result){
  1634. mainImgUrl = mainImgUrl.replace(result[0], '.'+result[1]);
  1635. }
  1636. if(!authority && i < execNum){
  1637. outObj.mainImg.push(mainImgUrl);
  1638. }
  1639. if(authority){
  1640. outObj.mainImg.push(mainImgUrl);
  1641. }
  1642. }
  1643. //sku图片
  1644. let arr2 = document.querySelectorAll('img[class^=SkuContent--valueItemImg--]');
  1645. if(arr2.length === 0){
  1646. arr2 = document.querySelectorAll('img[class*=valueItemImg--]');
  1647. }
  1648. for(let i=0; i< arr2.length; i++){
  1649. let skuImgUrl = arr2[i].src;
  1650. let result = regex.exec(skuImgUrl);
  1651. if(result){
  1652. skuImgUrl = skuImgUrl.replace(result[0], '.'+result[1]);
  1653. }
  1654. if(!authority && i < execNum){
  1655. outObj.skuImg.push(skuImgUrl);
  1656. }
  1657. if(authority){
  1658. outObj.skuImg.push(skuImgUrl);
  1659. }
  1660. }
  1661. //详情图片
  1662. let arr3 = document.querySelectorAll('#content img');
  1663. for(let i=0; i< arr3.length; i++){
  1664. let detailImgUrl = arr3[i].src;
  1665. let lazyUrl = arr3[i].getAttribute('data-src');
  1666. if(arr3[i].src.indexOf('/s.gif') > -1 && lazyUrl){
  1667. detailImgUrl = lazyUrl;
  1668. if(!/^http/.exec(lazyUrl)){
  1669. detailImgUrl = 'https:' + lazyUrl;
  1670. }
  1671. }
  1672. let result = regex.exec(detailImgUrl);
  1673. if(result){
  1674. detailImgUrl = detailImgUrl.replace(result[0], '.'+result[1]);
  1675. }
  1676. if(!authority && i < execNum){
  1677. outObj.detailImg.push(detailImgUrl);
  1678. }
  1679. if(authority){
  1680. outObj.detailImg.push(detailImgUrl);
  1681. }
  1682. }
  1683. //评论图片
  1684. let arr4 = document.querySelectorAll('div[class^=Comments--comments--] img');
  1685. if(arr4.length === 0){
  1686. arr4 = document.querySelectorAll('div[class^=comments--] img');
  1687. }
  1688. for(let i=0; i< arr4.length; i++){
  1689. if(arr4[i].src.indexOf('/avatar/sns/user/flag/sns_logo') == -1){ //过滤淘宝用户头像
  1690. if(!authority && i < execNum){
  1691. outObj.commentImg.push(arr4[i].src);
  1692. }
  1693. if(authority){
  1694. outObj.commentImg.push(arr4[i].src);
  1695. }
  1696. }
  1697. }
  1698. // 视频
  1699. let arr5 = document.querySelectorAll('video.lib-video');
  1700. for(let i=0; i< arr5.length; i++){
  1701. if(outObj.video.indexOf(arr5[i].src) == -1){
  1702. if(!authority && i < execNum){
  1703. outObj.video.push(arr5[i].src);
  1704. }
  1705. if(authority){
  1706. outObj.video.push(arr5[i].src);
  1707. }
  1708. }
  1709. }
  1710. return outObj;
  1711. }, authority, this.execNum);
  1712. if(imgInfo.video.length == 0 && responseVideo.length > 0){
  1713. imgInfo.video = responseVideo;
  1714. }
  1715. if(this.settingArr.indexOf('mainImg') > -1){
  1716. // 主图下载
  1717. for(let j = 0; j < imgInfo.mainImg.length; j++){
  1718. let fileName = imgInfo.mainImg[j].split('/').pop();
  1719. if(fileName){
  1720. let queryIndex = fileName.indexOf('?');
  1721. if (queryIndex !== -1) {
  1722. fileName = fileName.substr(0, queryIndex);
  1723. }
  1724. let num = Number(j) + 1;
  1725. let suffix = '';
  1726. if(fileName.lastIndexOf('.') > -1){
  1727. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1728. }
  1729. if (!fs.existsSync(urlInfo.newPath + '\\主图')) {
  1730. fs.mkdirSync(urlInfo.newPath + '\\主图');
  1731. }
  1732. let outputPath = urlInfo.newPath + '\\主图\\主图' + num + suffix;
  1733. await this.downloadImage(imgInfo.mainImg[j], outputPath, urlInfo);
  1734. }
  1735. }
  1736. }
  1737. // sku图片下载
  1738. if(this.settingArr.indexOf('skuImg') > -1){
  1739. for(let j = 0; j < imgInfo.skuImg.length; j++){
  1740. let fileName = imgInfo.skuImg[j].split('/').pop();
  1741. if(fileName){
  1742. let queryIndex = fileName.indexOf('?');
  1743. if (queryIndex !== -1) {
  1744. fileName = fileName.substr(0, queryIndex);
  1745. }
  1746. let num = Number(j) + 1;
  1747. let suffix = '';
  1748. if(fileName.lastIndexOf('.') > -1){
  1749. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1750. }
  1751. if (!fs.existsSync(urlInfo.newPath + '\\sku图')) {
  1752. fs.mkdirSync(urlInfo.newPath + '\\sku图');
  1753. }
  1754. let outputPath = urlInfo.newPath + '\\sku图\\sku图' + num + suffix;
  1755. await this.downloadImage(imgInfo.skuImg[j], outputPath, urlInfo);
  1756. }
  1757. }
  1758. }
  1759. //详情图下载
  1760. if(this.settingArr.indexOf('detailImg') > -1){
  1761. for(let j = 0; j < imgInfo.detailImg.length; j++){
  1762. let fileName = imgInfo.detailImg[j].split('/').pop();
  1763. if(fileName){
  1764. let queryIndex = fileName.indexOf('?');
  1765. if (queryIndex !== -1) {
  1766. fileName = fileName.substr(0, queryIndex);
  1767. }
  1768. let num = Number(j) + 1;
  1769. let suffix = '';
  1770. if(fileName.lastIndexOf('.') > -1){
  1771. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1772. }
  1773. if (!fs.existsSync(urlInfo.newPath + '\\详情图')) {
  1774. fs.mkdirSync(urlInfo.newPath + '\\详情图');
  1775. }
  1776. let outputPath = urlInfo.newPath + '\\详情图\\详情图' + num + suffix;
  1777. await this.downloadImage(imgInfo.detailImg[j], outputPath, urlInfo);
  1778. }
  1779. }
  1780. }
  1781. //评论图下载
  1782. if(this.settingArr.indexOf('commentImg') > -1){
  1783. for(let j = 0; j < imgInfo.commentImg.length; j++){
  1784. let fileName = imgInfo.commentImg[j].split('/').pop();
  1785. if(fileName){
  1786. let queryIndex = fileName.indexOf('?');
  1787. if (queryIndex !== -1) {
  1788. fileName = fileName.substr(0, queryIndex);
  1789. }
  1790. let num = Number(j) + 1;
  1791. let suffix = '';
  1792. if(fileName.lastIndexOf('.') > -1){
  1793. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1794. }
  1795. if (!fs.existsSync(urlInfo.newPath + '\\评论图')) {
  1796. fs.mkdirSync(urlInfo.newPath + '\\评论图');
  1797. }
  1798. let outputPath = urlInfo.newPath + '\\评论图\\评论图' + num + suffix;
  1799. await this.downloadImage(imgInfo.commentImg[j], outputPath, urlInfo);
  1800. }
  1801. }
  1802. }
  1803. //视频下载
  1804. if(this.settingArr.indexOf('video') > -1){
  1805. for(let j = 0; j < imgInfo.video.length; j++){
  1806. let fileName = imgInfo.video[j].split('/').pop();
  1807. if(fileName){
  1808. let queryIndex = fileName.indexOf('?');
  1809. if (queryIndex !== -1) {
  1810. fileName = fileName.substr(0, queryIndex);
  1811. }
  1812. if (!fs.existsSync(urlInfo.newPath + '\\视频')) {
  1813. fs.mkdirSync(urlInfo.newPath + '\\视频');
  1814. }
  1815. let outputPath = urlInfo.newPath + '\\视频\\' + fileName;
  1816. await this.downloadImage(imgInfo.video[j], outputPath, urlInfo);
  1817. }
  1818. }
  1819. }
  1820. await page.close();
  1821. urlInfo.status = '4';
  1822. resolve(true);
  1823. this.loading = false;
  1824. }
  1825. }, 300);
  1826. }catch(e){
  1827. reject(e);
  1828. this.showError(e);
  1829. }
  1830. })();
  1831. });
  1832. },
  1833. // 阿里巴巴 - 扫描并下载图片
  1834. async alibabaScanImg(browser, page, urlInfo, authority){
  1835. //detailImg:详情图;skuImg:sku图片;commentImg: 评论图;video: 视频
  1836. const imgInfo = await page.evaluate((authority, execNum) => {
  1837. let outObj = {
  1838. mainImg: [],
  1839. detailImg: [],
  1840. skuImg: [],
  1841. commentImg: [],
  1842. video: []
  1843. };
  1844. //主图
  1845. let arr1 = document.querySelectorAll('img[class*="-gallery-img"]');
  1846. for(let i=0; i< arr1.length; i++){
  1847. if(!authority && i < execNum){
  1848. outObj.mainImg.push(arr1[i].src);
  1849. }
  1850. if(authority){
  1851. outObj.mainImg.push(arr1[i].src);
  1852. }
  1853. }
  1854. //sku图片
  1855. let type = 'bg';
  1856. let arr2 = document.querySelectorAll('.prop-img');
  1857. if(arr2.length == 0){
  1858. arr2 = document.querySelectorAll('.sku-item-image');
  1859. }
  1860. if(arr2.length == 0){
  1861. arr2 = document.querySelectorAll('.ant-image-img');
  1862. type = 'img';
  1863. }
  1864. for(let i=0; i< arr2.length; i++){
  1865. let src;
  1866. if(type == 'bg'){
  1867. src = window.getComputedStyle(arr2[i]).backgroundImage.replace(/url\(["']?(.+?)["']?\)/i, '$1');
  1868. }
  1869. if(type == 'img'){
  1870. src = arr2[i].src;
  1871. }
  1872. if(!authority && i < execNum){
  1873. outObj.skuImg.push(src);
  1874. }
  1875. if(authority){
  1876. outObj.skuImg.push(src);
  1877. }
  1878. }
  1879. //详情图片
  1880. let arr3 = document.querySelectorAll('img.desc-img-loaded');
  1881. if(arr3.length == 0){
  1882. if(document.getElementById('description') && document.getElementById('description').querySelector('.html-description')){
  1883. arr3 = document.getElementById('description').querySelector('.html-description').shadowRoot.querySelectorAll('img');
  1884. }
  1885. }
  1886. for(let i=0; i< arr3.length; i++){
  1887. if(!authority && i < execNum){
  1888. outObj.detailImg.push(arr3[i].src);
  1889. }
  1890. if(authority){
  1891. outObj.detailImg.push(arr3[i].src);
  1892. }
  1893. }
  1894. // 视频
  1895. let arr5 = document.querySelectorAll('video.lib-video');
  1896. for(let i=0; i< arr5.length; i++){
  1897. if(outObj.video.indexOf(arr5[i].src) == -1){
  1898. if(!authority && i < execNum){
  1899. outObj.video.push(arr5[i].src);
  1900. }
  1901. if(authority){
  1902. outObj.video.push(arr5[i].src);
  1903. }
  1904. }
  1905. }
  1906. return outObj;
  1907. }, authority, this.execNum);
  1908. // 主图下载
  1909. if(this.settingArr.indexOf('mainImg') > -1){
  1910. for(let j = 0; j < imgInfo.mainImg.length; j++){
  1911. let fileName = imgInfo.mainImg[j].split('/').pop();
  1912. if(fileName){
  1913. let queryIndex = fileName.indexOf('?');
  1914. if (queryIndex !== -1) {
  1915. fileName = fileName.substr(0, queryIndex);
  1916. }
  1917. let num = Number(j) + 1;
  1918. let suffix = '';
  1919. if(fileName.lastIndexOf('.') > -1){
  1920. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1921. }
  1922. if (!fs.existsSync(urlInfo.newPath + '\\主图')) {
  1923. fs.mkdirSync(urlInfo.newPath + '\\主图');
  1924. }
  1925. let outputPath = urlInfo.newPath + '\\主图\\主图' + num + suffix;
  1926. await this.downloadImage(imgInfo.mainImg[j], outputPath, urlInfo);
  1927. }
  1928. }
  1929. }
  1930. // sku图片下载
  1931. if(this.settingArr.indexOf('skuImg') > -1){
  1932. for(let j = 0; j < imgInfo.skuImg.length; j++){
  1933. let fileName = imgInfo.skuImg[j].split('/').pop();
  1934. if(fileName){
  1935. let queryIndex = fileName.indexOf('?');
  1936. if (queryIndex !== -1) {
  1937. fileName = fileName.substr(0, queryIndex);
  1938. }
  1939. let num = Number(j) + 1;
  1940. let suffix = '';
  1941. if(fileName.lastIndexOf('.') > -1){
  1942. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1943. }
  1944. if (!fs.existsSync(urlInfo.newPath + '\\sku图')) {
  1945. fs.mkdirSync(urlInfo.newPath + '\\sku图');
  1946. }
  1947. let outputPath = urlInfo.newPath + '\\sku图\\sku图' + num + suffix;
  1948. await this.downloadImage(imgInfo.skuImg[j], outputPath, urlInfo);
  1949. }
  1950. }
  1951. }
  1952. //详情图下载
  1953. if(this.settingArr.indexOf('detailImg') > -1){
  1954. for(let j = 0; j < imgInfo.detailImg.length; j++){
  1955. let fileName = imgInfo.detailImg[j].split('/').pop();
  1956. if(fileName){
  1957. let queryIndex = fileName.indexOf('?');
  1958. if (queryIndex !== -1) {
  1959. fileName = fileName.substr(0, queryIndex);
  1960. }
  1961. let num = Number(j) + 1;
  1962. let suffix = '';
  1963. if(fileName.lastIndexOf('.') > -1){
  1964. suffix = fileName.substr(fileName.lastIndexOf('.'));
  1965. }
  1966. if (!fs.existsSync(urlInfo.newPath + '\\详情图')) {
  1967. fs.mkdirSync(urlInfo.newPath + '\\详情图');
  1968. }
  1969. let outputPath = urlInfo.newPath + '\\详情图\\' + '详情图' + num + suffix;
  1970. await this.downloadImage(imgInfo.detailImg[j], outputPath, urlInfo);
  1971. }
  1972. }
  1973. }
  1974. //视频下载
  1975. if(this.settingArr.indexOf('video') > -1){
  1976. for(let j = 0; j < imgInfo.video.length; j++){
  1977. let fileName = imgInfo.video[j].split('/').pop();
  1978. if(fileName){
  1979. let queryIndex = fileName.indexOf('?');
  1980. if (queryIndex !== -1) {
  1981. fileName = fileName.substr(0, queryIndex);
  1982. }
  1983. if (!fs.existsSync(urlInfo.newPath + '\\视频')) {
  1984. fs.mkdirSync(urlInfo.newPath + '\\视频');
  1985. }
  1986. let outputPath = urlInfo.newPath + '\\视频\\' + fileName;
  1987. await this.downloadImage(imgInfo.video[j], outputPath, urlInfo);
  1988. }
  1989. }
  1990. }
  1991. await page.close(); // 关闭页面但不关闭浏览器
  1992. },
  1993. // 检查小红书登录状态
  1994. checkRedLogin(){
  1995. this.checkLoading = true;
  1996. this.redStatus = 1;
  1997. return new Promise((resolve, reject) => {
  1998. (async () => {
  1999. try{
  2000. if(this.loginBrowser){
  2001. await this.loginBrowser.close();
  2002. this.loginBrowser = null;
  2003. }
  2004. puppeteer.use(StealthPlugin());
  2005. this.loginBrowser = await puppeteer.launch({
  2006. executablePath: this.initPath(),
  2007. userDataDir: os.tmpdir() + separator + 'chrome-data-capture',
  2008. args: [
  2009. '--start-maximized',
  2010. '--no-sandbox',
  2011. '--disable-setuid-sandbox',
  2012. '--disable-blink-features=AutomationControlled',
  2013. '--window-size=1280,800'
  2014. ],
  2015. });
  2016. const page = await this.loginBrowser.newPage();
  2017. await page.setViewport({ width: 1280, height: 800 });
  2018. let testUrl = "https://www.xiaohongshu.com";
  2019. await page.goto(testUrl, {waitUntil : 'networkidle2'});
  2020. let loginBtn = await page.$$('#login-btn');
  2021. let loginContainer = await page.$$('.login-container');
  2022. if(loginContainer.length > 0 || loginBtn.length > 0){
  2023. this.redStatus = 3; //未登录
  2024. }else{
  2025. this.redStatus = 2;
  2026. }
  2027. resolve(this.redStatus);
  2028. this.checkLoading = false;
  2029. await this.loginBrowser.close();
  2030. this.loginBrowser = null;
  2031. }catch(e){
  2032. this.checkLoading = false;
  2033. reject(3);
  2034. this.showError(e);
  2035. }
  2036. })();
  2037. });
  2038. },
  2039. // 5-小红书下载
  2040. async redDownload(urlInfo, redBrowser){
  2041. let task = await new Promise((resolve,reject) =>{
  2042. (async () => {
  2043. try{
  2044. let authority = this.$refs.headerRef.authority.isAuthority;
  2045. let number = 0;
  2046. urlInfo.status = '2';
  2047. urlInfo.num = 0;
  2048. // puppeteer.use(StealthPlugin());
  2049. // const redBrowser = await puppeteer.launch({
  2050. // headless: false,
  2051. // executablePath: this.initPath(),
  2052. // userDataDir: os.tmpdir() + separator + 'chrome-data-capture',
  2053. // args: [
  2054. // '--start-maximized',
  2055. // '--no-sandbox',
  2056. // '--disable-setuid-sandbox',
  2057. // '--disable-blink-features=AutomationControlled',
  2058. // ]
  2059. // });
  2060. const page = await redBrowser.newPage();
  2061. let responseVideo = [];
  2062. page.on('response', async(response) => {
  2063. // 检查响应的 MIME 类型是否以 'video/' 开头
  2064. if (response.headers()['content-type'] && response.headers()['content-type'].startsWith('video/')) {
  2065. if(responseVideo.indexOf(response.url()) < 0 && !response.url().startsWith('blob:https://')){
  2066. responseVideo.push(response.url());
  2067. }
  2068. }
  2069. });
  2070. await page.goto(urlInfo.url, {waitUntil : 'networkidle2'});
  2071. if(urlInfo.title){
  2072. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title)) {
  2073. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  2074. } else {
  2075. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title);
  2076. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + urlInfo.title;
  2077. }
  2078. }else{
  2079. await this.getTitle(page, urlInfo); // 生成页面标题对应的文件夹
  2080. }
  2081. urlInfo.status = '3';
  2082. // 定位元素
  2083. const handle = await page.$$('.pagination-media-container .pagination-item');
  2084. if(handle.length > 0){ // 模拟鼠标拖动 将鼠标移动到元素的中心,然后开始拖动
  2085. let handleLength = handle.length;
  2086. const box = await handle[0].boundingBox();
  2087. const boxEnd = await handle[handleLength - 1].boundingBox();
  2088. const center = {
  2089. x: box.x + box.width / 2,
  2090. y: box.y + box.height / 2
  2091. };
  2092. const centerEnd = {
  2093. x: boxEnd.x + boxEnd.width / 2,
  2094. y: boxEnd.y + boxEnd.height / 2
  2095. };
  2096. await page.mouse.move(center.x, center.y);
  2097. await page.mouse.down();
  2098. await page.mouse.move(centerEnd.x, centerEnd.y, { steps: 50 }); // 新的位置,可以根据需要调整步骤数
  2099. }
  2100. //detailImg:详情图;skuImg:sku图片;commentImg: 评论图;video: 视频
  2101. const imgInfo = await page.evaluate((authority, execNum) => {
  2102. let outObj = {
  2103. mainImg: [],
  2104. detailImg: [],
  2105. skuImg: [],
  2106. commentImg: [],
  2107. video: []
  2108. };
  2109. // 正则表达式匹配字符 重写图片路径
  2110. let regex = /\.(.{3,4})_[0-9a-zA-z]+\.(.{3,4})_\.(.{3,4})/;
  2111. //主图
  2112. let arr1 = document.querySelectorAll('img[class^=note-slider-img]');
  2113. for(let i=0; i< arr1.length; i++){
  2114. let mainImgUrl = arr1[i].src;
  2115. let result = regex.exec(mainImgUrl);
  2116. if(result){
  2117. mainImgUrl = mainImgUrl.replace(result[0], '.'+result[1]);
  2118. }
  2119. if(!authority && i < execNum){
  2120. outObj.mainImg.push(mainImgUrl);
  2121. }
  2122. if(authority){
  2123. outObj.mainImg.push(mainImgUrl);
  2124. }
  2125. }
  2126. // 视频
  2127. let arr5 = document.querySelectorAll('video.lib-video');
  2128. for(let i=0; i< arr5.length; i++){
  2129. if(outObj.video.indexOf(arr5[i].src) == -1){
  2130. if(!authority && i < execNum){
  2131. outObj.video.push(arr5[i].src);
  2132. }
  2133. if(authority){
  2134. outObj.video.push(arr5[i].src);
  2135. }
  2136. }
  2137. }
  2138. return outObj;
  2139. }, authority, this.execNum);
  2140. if(imgInfo.mainImg.length >= 3){
  2141. imgInfo.mainImg = imgInfo.mainImg.slice(1, imgInfo.mainImg.length-1); //小红书轮播图第一个/最后一个和内容重复去掉
  2142. }
  2143. imgInfo.video = responseVideo;
  2144. // 主图下载
  2145. for(let j = 0; j < imgInfo.mainImg.length; j++){
  2146. let fileName = imgInfo.mainImg[j].split('/').pop();
  2147. if(fileName){
  2148. let queryIndex = fileName.indexOf('?');
  2149. if (queryIndex !== -1) {
  2150. fileName = fileName.substr(0, queryIndex);
  2151. }
  2152. let num = Number(j) + 1;
  2153. let suffix = '.jpg';
  2154. if(fileName.lastIndexOf('.') > -1){
  2155. suffix = fileName.substr(fileName.lastIndexOf('.'));
  2156. }
  2157. let outputPath = urlInfo.newPath + '\\文章图' + num + suffix;
  2158. await this.downloadImage(imgInfo.mainImg[j], outputPath, urlInfo);
  2159. }
  2160. }
  2161. //视频下载
  2162. for(let j = 0; j < imgInfo.video.length; j++){
  2163. let fileName = imgInfo.video[j].split('/').pop();
  2164. if(fileName){
  2165. let queryIndex = fileName.indexOf('?');
  2166. if (queryIndex !== -1) {
  2167. fileName = fileName.substr(0, queryIndex);
  2168. }
  2169. let num = Number(j) + 1;
  2170. let suffix = '.mp4';
  2171. if(fileName.lastIndexOf('.') > -1){
  2172. suffix = fileName.substr(fileName.lastIndexOf('.'));
  2173. }
  2174. let outputPath = urlInfo.newPath + '\\视频' + num + suffix;
  2175. await this.downloadImage(imgInfo.video[j], outputPath, urlInfo);
  2176. }
  2177. }
  2178. await page.close();
  2179. urlInfo.status = '4';
  2180. resolve(true);
  2181. this.loading = false;
  2182. }catch(e){
  2183. urlInfo.status = '5';
  2184. reject(e);
  2185. this.showError(e);
  2186. }
  2187. })();
  2188. });
  2189. },
  2190. //
  2191. downloadExample(){
  2192. let url = 'https://www.xingyousoft.com/soft/XYCapture/example.xlsx';
  2193. if (!fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName)) {
  2194. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName);
  2195. }
  2196. let path = this.downloadDir + separator + pjson.softInfo.softName + '\\链接模板下载.xlsx';
  2197. this.downloadImage(url, path, 'example');
  2198. },
  2199. // 下载网址链接的图片
  2200. async downloadImage(imageUrl, outputPath, urlInfo) {
  2201. let _this = this;
  2202. let received_bytes = 0;
  2203. let total_bytes = 0;
  2204. try {
  2205. let req = request({
  2206. method: 'GET', uri: imageUrl, strictSSL: false
  2207. });
  2208. let out = fs.createWriteStream(outputPath);
  2209. req.pipe(out);
  2210. return new Promise((resolve, reject) => {
  2211. req.on('response', (data) => {
  2212. total_bytes = parseInt(data.headers['content-length']);
  2213. const status = data.statusCode;
  2214. if (status < 200 || status >= 300) {
  2215. //reject(false);
  2216. this.$notify.error({
  2217. title: '网络图片访问异常!- 1',
  2218. message: imageUrl.slice(0,50)
  2219. });
  2220. }else if(isNaN(total_bytes)){
  2221. //reject(false);
  2222. this.$notify.error({
  2223. title: '网络图片访问异常!- 2',
  2224. message: imageUrl.slice(0,50)
  2225. });
  2226. }else{
  2227. // console.log('下载中...')
  2228. }
  2229. });
  2230. req.on('data', (chunk) => {
  2231. received_bytes += chunk.length;
  2232. });
  2233. req.on('end', ()=> {
  2234. if(urlInfo != 'example'){
  2235. urlInfo.num += 1;
  2236. }else{
  2237. this.$msgbox({
  2238. title: '消息',
  2239. message: '模板下载成功,保存位置:' + this.downloadDir + separator + pjson.softInfo.softName,
  2240. showCancelButton: true,
  2241. confirmButtonText: '去查看',
  2242. cancelButtonText: '取消',
  2243. }).then(action => {
  2244. this.openFolder();
  2245. }).catch(action => {
  2246. });
  2247. }
  2248. //console.log('下载完成', outputPath)
  2249. resolve(true);
  2250. });
  2251. });
  2252. } catch (error) {
  2253. console.error(imageUrl, `Failed to download image: ${error.message}`);
  2254. throw error;
  2255. }
  2256. },
  2257. // 获取页面标题 - 生成对应的文件夹
  2258. async getTitle(page, urlInfo){
  2259. // 已页面标题作为新建文件夹,保留前50个字
  2260. let title = await page.title();
  2261. if(title){
  2262. title = title.substring(0, 50);
  2263. if(this.containsAnyChar(title, ['\\', '/', ':', '*', '?', '"', '<', '>', '|'])){ //判断是否含有特殊字符
  2264. title = title.replace(/[\\|/|:|*|?|"|<|>||]/g, "");
  2265. }
  2266. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName + separator + title)) {
  2267. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + title;
  2268. } else {
  2269. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName + separator + title);
  2270. urlInfo.newPath = this.downloadDir + separator + pjson.softInfo.softName + separator + title;
  2271. }
  2272. }
  2273. },
  2274. // 下载base64位的图片
  2275. async downloadBaseImage(base64String, outputPath, urlInfo) {
  2276. const buffer = Buffer.from(base64String, 'base64');
  2277. const writeStream = fs.createWriteStream(outputPath);
  2278. writeStream.write(buffer);
  2279. writeStream.end();
  2280. writeStream.on('finish', () => {
  2281. urlInfo.num += 1;
  2282. });
  2283. // 监听错误事件
  2284. writeStream.on('error', (err) => {
  2285. console.error('base64位写入文件时出错:', err);
  2286. });
  2287. },
  2288. // 错误提示
  2289. showError(e){
  2290. let str = '';
  2291. if(e.toString().indexOf('ERR_NAME_NOT_RESOLVE') > -1){
  2292. str = '无法解析该网址,请查看网址是否正确!-1';
  2293. }else if(e.toString().indexOf('Cannot navigate to invalid URL') > -1){
  2294. str = '无效的网址,请查看网址格式是否正确!-2';
  2295. }else if(e.toString().indexOf('ERR_CONNECTION_TIMED_OUT') > -1){
  2296. str = '链接请求超时,请查看网络状态!-3';
  2297. }else if(e.toString().indexOf('TimeoutError') > -1){
  2298. str = '链接请求超时,请查看网络状态!-4';
  2299. }else if(e.toString().indexOf('operation not permitted') > -1){
  2300. str = '权限受限,请以管理员权限运行软件!-5';
  2301. }else if(e.toString().indexOf('browser has disconnected') > -1){
  2302. str = '内置浏览器已关闭!-6';
  2303. }else if(e.toString().indexOf('Failed to launch the browser') > -1){
  2304. str = '请先关闭内置浏览器!-7';
  2305. }else{
  2306. str = e.toString();
  2307. //console.log(e);
  2308. }
  2309. this.loading = false;
  2310. this.$notify.error({
  2311. title: '提示',
  2312. message: str
  2313. });
  2314. },
  2315. // 是否包含特殊字符
  2316. containsAnyChar(str, charsArray) {
  2317. for (let i = 0; i < charsArray.length; i++) {
  2318. if (str.includes(charsArray[i])) {
  2319. return true;
  2320. }
  2321. }
  2322. return false;
  2323. },
  2324. // 随机生成字符
  2325. randomString(length) {
  2326. let result = '';
  2327. const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  2328. const charactersLength = characters.length;
  2329. for (let i = 0; i < length; i++ ) {
  2330. result += characters.charAt(Math.floor(Math.random() * charactersLength));
  2331. }
  2332. return result;
  2333. }
  2334. }
  2335. };
  2336. </script>
  2337. <style lang="scss">
  2338. @import "../assets/css/fontx/iconfont.css";
  2339. @import "../assets/css/home.scss";
  2340. .ivu-input-number-controls-outside-btn i {
  2341. font-weight: 800;
  2342. }
  2343. .update-point {
  2344. display: inline-block;
  2345. width: 8px;
  2346. height: 8px;
  2347. border-radius: 8px;
  2348. background: #ff0000;
  2349. top: 14px;
  2350. position: absolute;
  2351. left: -13px;
  2352. }
  2353. .menu-item {
  2354. padding: 8px 0;
  2355. font-size: 14px;
  2356. .iconfont {
  2357. font-size: 32px;
  2358. }
  2359. &:hover,
  2360. &.active {
  2361. color: #ed4014;
  2362. }
  2363. }
  2364. .ivu-progress-show-info .ivu-progress-outer {
  2365. padding-right: 40px !important;
  2366. margin-right: -40px !important;
  2367. }
  2368. .tips {
  2369. text-align: center;
  2370. padding: 10px 0;
  2371. color: #ed4014;
  2372. font-size: 12px;
  2373. }
  2374. .handle-desc {
  2375. display: inline-block;
  2376. width: calc(100% - 100px);
  2377. overflow: hidden;
  2378. }
  2379. .ivu-menu-submenu-title {
  2380. font-weight: 600;
  2381. }
  2382. .ivu-menu .ivu-menu-item {
  2383. line-height: 1;
  2384. }
  2385. // new-el
  2386. .el-menu {
  2387. border-right: none !important;
  2388. }
  2389. .cmenu-item {
  2390. padding: 0 20px 20px;
  2391. margin-bottom: 15px;
  2392. .cmenu-title {
  2393. font-size: 18px;
  2394. font-weight: 600;
  2395. padding-bottom: 20px;
  2396. }
  2397. .citem-nav {
  2398. text-align: center;
  2399. border-radius: 10px;
  2400. min-height: 110px;
  2401. padding: 15px 0;
  2402. background-color: #fff;
  2403. font-size: 15px;
  2404. cursor: pointer;
  2405. &.bg-linear1 {
  2406. color: #fff;
  2407. font-size: 20px;
  2408. background: linear-gradient(to right bottom, #2A56CA, #5795F4);
  2409. }
  2410. &.bg-linear2 {
  2411. color: #fff;
  2412. font-size: 20px;
  2413. background: linear-gradient(to right top, #147FBB, #5EB3E3);
  2414. }
  2415. &.bg-linear3 {
  2416. color: #fff;
  2417. font-size: 20px;
  2418. background: linear-gradient(to right bottom, #2F9E8A, #56CDB1);
  2419. }
  2420. &:hover {
  2421. margin-top: -5px;
  2422. box-shadow: 3px 3px 6px #ccc, -3px -3px 6px #ccc;
  2423. }
  2424. .citem-img {
  2425. width: 50px;
  2426. margin-bottom: 10px;
  2427. }
  2428. }
  2429. }
  2430. .popper-open{
  2431. text-align: center !important;
  2432. padding: 10px !important;
  2433. background: #303133 !important;
  2434. color: #fff !important;
  2435. min-width: 120px !important;
  2436. opacity: 0.8;
  2437. }
  2438. .popper-open[x-placement^=bottom] .popper__arrow::after{
  2439. border-bottom-color: #303133 !important;
  2440. }
  2441. textarea.el-textarea__inner{
  2442. height: 100% !important;
  2443. font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
  2444. }
  2445. .set-item{
  2446. margin-right: 15px;
  2447. .set-title{
  2448. font-size: 13px;
  2449. }
  2450. }
  2451. .outarea{
  2452. height: 100%;
  2453. border: 1px solid #DCDFE6;
  2454. background-color: #4851a415;
  2455. padding: 15px;
  2456. font-size: 20px;
  2457. overflow: hidden auto;
  2458. }
  2459. .outtext{
  2460. height: 100%;
  2461. font-size: 20px;
  2462. overflow: hidden auto;
  2463. textarea{
  2464. background-color: #4851a415;
  2465. }
  2466. }
  2467. .pin-tips{
  2468. font-size: 14px;
  2469. position: absolute;
  2470. bottom: 10px;
  2471. color: #ff0000;
  2472. left: 0;
  2473. right: 0;
  2474. margin: auto;
  2475. text-align: center;
  2476. }
  2477. .outarea.red-border{
  2478. border: 1px solid #F56C6C;
  2479. }
  2480. .outtext.red-border textarea{
  2481. border: 1px solid #F56C6C;
  2482. }
  2483. h3{
  2484. margin: 0;
  2485. }
  2486. .m-image{
  2487. width: 20px;
  2488. margin-right: 5px;
  2489. }
  2490. .dialog-footer-center{
  2491. text-align: center;
  2492. }
  2493. .visible-tips-style{
  2494. font-size: 18px;
  2495. color: #f73131;
  2496. font-weight: 600;
  2497. padding: 30px 40px 0;
  2498. }
  2499. .no-select{
  2500. user-select: none;
  2501. }
  2502. .tips-flex{
  2503. display: flex;
  2504. flex-wrap: nowrap;
  2505. justify-content: space-around;
  2506. align-items: center;
  2507. .el-icon-s-opportunity{
  2508. font-size: 50px;
  2509. color: #f73131;
  2510. margin-right: 10px;
  2511. }
  2512. .m-title{
  2513. flex: 1;
  2514. color: #f73131;
  2515. }
  2516. }
  2517. </style>