RunSpider.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <template>
  2. <div class="dialog-content">
  3. <div class="flex gap-2">
  4. <el-space class="edit-tag-list">
  5. <el-tag class="edit-tag" type="primary"> {{ formData.code }}</el-tag>
  6. <el-tag class="edit-tag" type="success"> {{ formData.site }}</el-tag>
  7. <el-tag class="edit-tag" type="info"> {{ formData.channel }}</el-tag>
  8. <el-tag class="edit-tag" type="warning">{{ formData.href }}</el-tag>
  9. <el-tag class="edit-tag" type="primary"> {{ formData.modifyuser }}</el-tag>
  10. </el-space>
  11. </div>
  12. <div class="space"></div>
  13. <el-form ref="form" :model="formData" label-width="120px">
  14. <el-row>
  15. <el-col :span="8">
  16. <el-form-item label="URL">
  17. <el-input v-model="formData.href" disabled :title="formData.href"></el-input>
  18. </el-form-item>
  19. </el-col>
  20. <el-col :span="8"><el-form-item label="代理地址">
  21. <el-input v-model="formData.proxyServe"></el-input>
  22. </el-form-item></el-col>
  23. <el-col :span="8"><el-form-item label="最大页数">
  24. <el-input v-model="formData.maxPages"></el-input>
  25. </el-form-item></el-col>
  26. </el-row>
  27. <el-row>
  28. <el-col :span="8"><el-form-item label="列表延时(MS)">
  29. <el-input v-model="formData.listDelay"></el-input>
  30. </el-form-item></el-col>
  31. <el-col :span="8"><el-form-item label="翻页延时(MS)">
  32. <el-input v-model="formData.trunPageDelay"></el-input>
  33. </el-form-item></el-col>
  34. <el-col :span="8"><el-form-item label="详情延时(MS)">
  35. <el-input v-model="formData.contentDelay"></el-input>
  36. </el-form-item></el-col>
  37. </el-row>
  38. <el-row>
  39. <el-col :span="8"><el-form-item label="浏览器">
  40. <el-radio-group v-model="formData.headless">
  41. <el-radio :value="true">无头</el-radio>
  42. <el-radio :value="false">显式</el-radio>
  43. </el-radio-group> </el-form-item>
  44. </el-col>
  45. <el-col :span="8"><el-form-item label="显示图像">
  46. <el-radio-group v-model="formData.showImage">
  47. <el-radio :value="true">显示</el-radio>
  48. <el-radio :value="false">不显示</el-radio>
  49. </el-radio-group> </el-form-item>
  50. </el-col>
  51. <el-col :span="8"><el-form-item label="开启线程数">
  52. <el-input v-model="formData.threads"></el-input>
  53. </el-form-item></el-col>
  54. </el-row>
  55. </el-form>
  56. <div style="text-align: center;">
  57. <el-space>
  58. <el-button type="primary" @click="handleDebug"><el-icon>
  59. <VideoPlay />
  60. </el-icon>执行</el-button>
  61. <el-button type="primary" @click="handleStop"><el-icon>
  62. <VideoPause />
  63. </el-icon>终止</el-button>
  64. <el-button type="primary" @click="handleRefersh"><el-icon>
  65. <Refresh />
  66. </el-icon>刷新结果</el-button>
  67. <!-- <el-button type="primary" @click="handleCountYestday"><el-icon>
  68. <Refresh />
  69. </el-icon>统计昨日信息发布量</el-button> -->
  70. <el-dropdown>
  71. <el-button type="primary">
  72. 结果导出<el-icon class="el-icon--right"><arrow-down /></el-icon>
  73. </el-button>
  74. <template #dropdown>
  75. <el-dropdown-menu>
  76. <el-dropdown-item @click="handleExportEpub">导出EPUB格式文件</el-dropdown-item>
  77. <el-dropdown-item @click="handleExportJson">导出JSON格式文件</el-dropdown-item>
  78. <el-dropdown-item @click="handleExportExcel">导出Excel格式文件</el-dropdown-item>
  79. <!-- <el-dropdown-item>补录/上推至平台</el-dropdown-item> -->
  80. </el-dropdown-menu>
  81. </template>
  82. </el-dropdown></el-space>
  83. </div>
  84. <el-divider />
  85. <div id="debugEventContian">执行日志:&nbsp;{{ debugLogLine }}</div>
  86. <el-divider />
  87. <el-table :data="tableData" style="width: 100%" :height="tableHeight" @row-click="handleRowClick">
  88. <el-table-column prop="no" label="序号" width="90" />
  89. <el-table-column prop="title" label="标题" width="240" show-overflow-tooltip />
  90. <el-table-column prop="href" label="链接" show-overflow-tooltip />
  91. <el-table-column prop="contentShort" label="正文" show-overflow-tooltip />
  92. </el-table>
  93. </div>
  94. <ViewArticle ref="articleDialog" />
  95. </template>
  96. <script setup>
  97. import { ref, computed } from 'vue';
  98. import { ElMessage, ElMessageBox } from 'element-plus'
  99. import ViewArticle from "./ViewArticle.vue"
  100. import {ViewCurrentSpiderConfig, DebugSpider, StopDebugSpider, ExportJsonFile} from "../../../wailsjs/go/main/App"
  101. import { ViewResultItemAll, SelectSaveFilePath, ExportEpubFile, ExportExcelFile, CountYestodayArts } from "../../../wailsjs/go/main/App"
  102. import { EventsOn, EventsOff } from "../../../wailsjs/runtime"
  103. let originData = {}
  104. const defaultFormValue = {
  105. maxPages: 1,
  106. }
  107. const formData = ref({
  108. // tags
  109. code: '',
  110. site: '',
  111. channel: '',
  112. href: '',
  113. modifyuser: '',
  114. // form
  115. proxyServe: '',
  116. maxPages: defaultFormValue.maxPages,
  117. listDelay: '',
  118. trunPageDelay: '',
  119. contentDelay: '',
  120. headless: false,
  121. showImage: false,
  122. threads: '1',
  123. cssmark: {}
  124. })
  125. const articleDialog = ref(null)
  126. const debugLogLine = ref("")
  127. const tableData = ref([])
  128. const tableHeight = computed(() => window.innerHeight - 480 + 'px');
  129. const setPageData = (e) => {
  130. originData = e
  131. const cssMark = e.cssmark
  132. formData.value.code = e.code
  133. formData.value.site = e.site
  134. formData.value.channel = e.channel
  135. formData.value.href = e.href
  136. formData.value.modifyuser = e.modifyuser
  137. if (cssMark) {
  138. // form
  139. // formData.value.maxPages = cssMark.maxPages
  140. formData.value.listDelay = cssMark.listDelayTime
  141. formData.value.trunPageDelay = cssMark.listTurnDelayTime
  142. formData.value.contentDelay = cssMark.contentDelayTime
  143. formData.value.cssmark = cssMark
  144. }
  145. }
  146. //开始调试
  147. const handleDebug = () => {
  148. ElMessage({
  149. message: `${[formData.value.href, formData.value.listDelay, formData.value.contentDelay,parseInt(formData.value.trunPageDelay),
  150. formData.value.headless, formData.value.showImage, formData.value.proxyServe, formData.value.threads,
  151. formData.value.maxPages].join("//")}!`,
  152. showClose: true,
  153. duration: 3000,
  154. });
  155. const mark = {
  156. ...formData.value.cssmark,
  157. code: formData.value.code,
  158. site: formData.value.site,
  159. channel: formData.value.channel,
  160. href: formData.value.href,
  161. modifyuser: formData.value.modifyuser,
  162. }
  163. DebugSpider(
  164. formData.value.href,
  165. formData.value.proxyServe,
  166. parseInt(formData.value.maxPages),
  167. parseInt(formData.value.listDelay),
  168. parseInt(formData.value.trunPageDelay),
  169. parseInt(formData.value.contentDelay),
  170. formData.value.headless,
  171. formData.value.showImage,
  172. parseInt(formData.value.threads),
  173. mark,
  174. )
  175. }
  176. //停止调试
  177. const handleStop = () => {
  178. StopDebugSpider()
  179. }
  180. //
  181. const truncateString = (str, maxLength) => {
  182. return str.substring(0, maxLength) + "..";
  183. }
  184. //刷新加载数据
  185. const handleRefersh = () => {
  186. ViewResultItemAll(formData.value.code).then(result => {
  187. //result = result.slice(-20);
  188. result.forEach((v, i) => {
  189. v.contentShort = truncateString(v.content, 50)
  190. })
  191. tableData.value = result
  192. })
  193. }
  194. //handleExportEpub导出文件
  195. const handleExportEpub = () => {
  196. ElMessageBox.prompt('请输入文件名称', '文件名', {
  197. confirmButtonText: '确定',
  198. cancelButtonText: '取消',
  199. }).then(({ value }) => {
  200. SelectSaveFilePath("", value,"epub").then(save2file => {
  201. if (save2file == "") {
  202. console.log("无效的文件存储路径", save2file)
  203. return
  204. }
  205. ExportEpubFile(value, save2file, formData.value.code).then(d => {
  206. if (d.err === 1) {
  207. ElMessage({
  208. message: d.msg || `导出epub文件${save2file}完成!`,
  209. type: 'success',
  210. duration: 3000,
  211. });
  212. } else {
  213. ElMessage({
  214. message: d.msg || `导出epub文件${save2file}失败!`,
  215. type: 'error',
  216. duration: 3000,
  217. });
  218. }
  219. })
  220. })
  221. })
  222. }
  223. const handleExportJson = () => {
  224. ElMessageBox.prompt('请输入文件名称', '文件名', {
  225. confirmButtonText: '确定',
  226. cancelButtonText: '取消',
  227. }).then(({ value }) => {
  228. SelectSaveFilePath("", value,"json").then(save2file => {
  229. console.log("json",save2file)
  230. if (save2file == "") {
  231. console.log("无效的文件存储路径", save2file)
  232. return
  233. }
  234. ExportJsonFile(save2file, formData.value.code).then(d => {
  235. if (d.err === 1) {
  236. ElMessage({
  237. message: d.msg || `导出excel文件${save2file}完成!`,
  238. type: 'success',
  239. duration: 3000,
  240. });
  241. } else {
  242. ElMessage({
  243. message: d.msg || `导出excel文件${save2file}失败!`,
  244. type: 'error',
  245. duration: 3000,
  246. });
  247. }
  248. })
  249. })
  250. })
  251. }
  252. const handleExportExcel = () => {
  253. ElMessageBox.prompt('请输入文件名称', '文件名', {
  254. confirmButtonText: '确定',
  255. cancelButtonText: '取消',
  256. }).then(({ value }) => {
  257. SelectSaveFilePath("", value,"xlsx").then(save2file => {
  258. console.log("excel",save2file)
  259. if (save2file == "") {
  260. console.log("无效的文件存储路径", save2file)
  261. return
  262. }
  263. ExportExcelFile(save2file, formData.value.code).then(d => {
  264. if (d.err === 1) {
  265. ElMessage({
  266. message: d.msg || `导出excel文件${save2file}完成!`,
  267. type: 'success',
  268. duration: 3000,
  269. });
  270. } else {
  271. ElMessage({
  272. message: d.msg || `导出excel文件${save2file}失败!`,
  273. type: 'error',
  274. duration: 3000,
  275. });
  276. }
  277. })
  278. })
  279. })
  280. }
  281. const replaceAll = function (src, search, replacement) {
  282. return src.split(search).join(replacement);
  283. };
  284. //行点击事件
  285. const handleRowClick = (row, column, event) => {
  286. articleDialog.value.dialogVisible = true
  287. row.content = replaceAll(row.content, '\n', '<br/>')
  288. articleDialog.value.formData = row
  289. articleDialog.value.scrollTop()
  290. }
  291. //
  292. const handleCountYestday = () => {
  293. if (formData.value.listNextPageCss != "" && formData.value.listPublishTimeCss != "") {
  294. ElMessage({
  295. message: `${[formData.value.url, formData.value.listDelay, formData.value.contentDelay,
  296. formData.value.headless, formData.value.showImage, formData.value.proxyServe].join("//")}!`,
  297. showClose: true,
  298. duration: 3000,
  299. });
  300. CountYestodayArts(formData.value.url, parseInt(formData.value.listDelay), parseInt(formData.value.contentDelay),
  301. formData.value.headless == 'true', formData.value.showImage == 'true')
  302. } else {
  303. ElMessage({
  304. message: "当前爬虫设置,CSS选择器,不具备列表页发布时间+列表页翻页。",
  305. type: 'error',
  306. showClose: true,
  307. duration: 3000,
  308. });
  309. }
  310. }
  311. //Wails事件绑定
  312. EventsOff('debug_event')
  313. EventsOn("debug_event", data => {
  314. debugLogLine.value = data
  315. })
  316. //加载当前爬虫配置
  317. // ViewCurrentSpiderConfig().then(result => {
  318. // console.log(result)
  319. // // result['listDelay'] = 500
  320. // // result['contentDelay'] = 500
  321. // // result['proxyServe'] = ''
  322. // // result['showImage'] = 'false'
  323. // // result['headless'] = 'false'
  324. // // formData.value = { ...result }
  325. // })
  326. defineExpose({
  327. setPageData,
  328. })
  329. </script>
  330. <style scoped>
  331. .dialog-content {
  332. max-height: 60vh;
  333. overflow-y: scroll;
  334. }
  335. .edit-tag-list {
  336. flex-wrap: wrap;
  337. }
  338. .edit-tag {
  339. margin-bottom: 4px;
  340. }
  341. </style>