Compare commits

...

1 Commits

Author SHA1 Message Date
liliang@qniao.cn 1b9b6ada10 添加src/views/sys/scrapy/index.vue爬虫页面 3 years ago
16 changed files with 630 additions and 13 deletions
Split View
  1. 4
      .env.development
  2. 4
      .eslintrc.js
  3. 1
      spider-management-backend-web
  4. 39
      src/api/sys/model/scrapyModel.ts
  5. 56
      src/api/sys/scrapy.ts
  6. 1
      src/layouts/default/content/index.vue
  7. 30
      src/router/routes/modules/scrapy.ts
  8. 17
      src/store/modules/scrapy.ts
  9. 70
      src/utils/http/axios/index.ts
  10. 1
      src/views/sys/login/useLogin.ts
  11. 81
      src/views/sys/scrapy/createSpider.vue
  12. 259
      src/views/sys/scrapy/index.vue
  13. 55
      src/views/sys/scrapy/product.data.ts
  14. 11
      types/axios.d.ts
  15. 2
      vite.config.ts
  16. 12
      yarn.lock

4
.env.development

@ -6,7 +6,9 @@ VITE_PUBLIC_PATH = /
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
#本地跨域代理
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/basic-api","http://8.135.1.175:7023"],["/upload","http://localhost:3300/upload"]]
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
# Delete console

4
.eslintrc.js

@ -16,9 +16,7 @@ module.exports = {
},
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
"plugin:prettier/recommended","prettier"
],
rules: {
'vue/script-setup-uses-vars': 'error',

1
spider-management-backend-web

@ -1 +0,0 @@
Subproject commit f25cfcc7beafd63ca7c80757785ac3f0cf4b9a3a

39
src/api/sys/model/scrapyModel.ts

@ -0,0 +1,39 @@
/**
* @description: getScrapList interface parameters
*/
export interface getScrapyParams{
spiderName: string;
status: string;
}
/**
* @description: Getscray interface return value
*/
export interface getScrapyInfoModel{
records: recordsModel[];
total: number;
size:number;
current: number;
pages:number;
}
export interface recordsModel{
id: number;
createdTime: string;
updatedTime: string;
spiderName: string;
duration: number;
status: number;
}
export interface createModel{
id:number;
}
export interface getStauts{
key: number;
value: string;
}

56
src/api/sys/scrapy.ts

@ -0,0 +1,56 @@
import { defHttp } from "/@/utils/http/axios";
import { createModel, getStauts, recordsModel } from "/@/api/sys/model/scrapyModel";
enum Api {
getScrapyInfo = '/admin/query/spider',
createSpider = '/admin/create/spider',
runSpider = '/admin/run/spider',
getSpiderStatus = '/admin/get/spider/status',
stopSpiderDuration = '/admin/stop/spider'
}
/**
* @description:
*/
export function getScrapyApi(params) {
return defHttp.get<recordsModel>(
{
url: Api.getScrapyInfo,
params
},
);
}
/**
* @description:
*/
export function createSpiderApi(params) {
return defHttp.post<createModel>(
{
url: Api.createSpider,
params
},
);
}
/**
* @description:
*/
export function runSpiderApi(params) {
return defHttp.post<recordsModel>(
{
url: Api.runSpider,
params,
},
);
}
export function getStatusApi(){
return defHttp.get<getStauts>(
{
url:Api.getSpiderStatus,
},
);
}

1
src/layouts/default/content/index.vue

@ -36,7 +36,6 @@
position: relative;
flex: 1 1 auto;
min-height: 0;
// begin: (2K 31 ) bug , , ?
&.fixed {
width: 1200px;

30
src/router/routes/modules/scrapy.ts

@ -0,0 +1,30 @@
import type { AppRouteModule } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
// import { t } from '/@/hooks/web/useI18n';
const scrapy: AppRouteModule = {
path: '/scrapy',
name: 'scrapyName',
component: LAYOUT,
redirect: '/scrapy/index',
meta: {
orderNo: 200,
icon: 'ion:git-compare-outline',
title: '爬虫',
},
children: [
{
path: 'index',
name: 'ScrapyPage',
component: () => import('/@/views/sys/scrapy/index.vue'),
meta: {
title: '爬虫页面',
icon: 'simple-icons:about-dot-me',
hideMenu: true,
},
},
],
};
export default scrapy;

17
src/store/modules/scrapy.ts

@ -0,0 +1,17 @@
// import { defineStore } from "pinia";
// import { getScrapyInfoModel, getScrapyParams } from "/@/api/sys/model/scrapyModel";
// import { getScrapyApi } from "/@/api/sys/scrapy";
// export const useScrapyStore = defineStore({
// async getScrapyInfo(
// params: getScrapyParams
// ):Promise<getScrapyInfoModel | null>{
// try {
// const { ...getScrapyParams} = params
// const data = await getScrapyApi(getScrapyParams);
// return data
// }catch (error){
// return Promise.reject(error);
// }
// }
// })

70
src/utils/http/axios/index.ts

@ -51,11 +51,11 @@ const transform: AxiosTransform = {
throw new Error(t('sys.api.apiRequestFailed'));
}
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
const { code, result, message } = data;
const { code, message, result} = data; //const { current pages records size total } = data
// 这里逻辑可以根据项目进行修改
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
if (hasSuccess) {
//const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
// if (hasSuccess) {
let successMsg = message;
if (isNull(successMsg) || isUnDef(successMsg) || isEmpty(successMsg)) {
@ -68,7 +68,7 @@ const transform: AxiosTransform = {
createMessage.success(successMsg);
}
return result;
}
// }
// 在此处根据自己项目的实际情况对不同的code执行不同的操作
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
@ -259,7 +259,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
// 接口拼接地址
urlPrefix: urlPrefix,
// 是否加入时间戳
joinTime: true,
joinTime: false,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
@ -275,6 +275,9 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
),
);
}
export const defHttp = createAxios();
// other api url
@ -284,3 +287,60 @@ export const defHttp = createAxios();
// urlPrefix: 'xxx',
// },
// });
function createAxios1(opt?: Partial<CreateAxiosOptions>) {
return new VAxios(
// 深度合并
deepMerge(
{
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
// authentication schemes,e.g: Bearer
// authenticationScheme: 'Bearer',
authenticationScheme: '',
timeout: 10 * 1000,
// 基础接口地址
// baseURL: globSetting.apiUrl,
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 如果是form-data格式
// headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
// 数据处理方式
transform: clone(transform),
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'message',
// 接口地址
apiUrl: globSetting.apiUrl,
// 接口拼接地址
urlPrefix: urlPrefix,
// 是否加入时间戳
joinTime: false,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
retryRequest: {
isOpenRetry: true,
count: 5,
waitTime: 100,
},
},
},
opt || {},
),
);
}
export const Http = createAxios1()

1
src/views/sys/login/useLogin.ts

@ -35,7 +35,6 @@ export function useFormValid<T extends Object = any>(formRef: Ref<FormInstance>)
const form = unref(formRef);
return form?.validate ?? ((_nameList?: NamePath) => Promise.resolve());
});
async function validForm() {
const form = unref(formRef);
if (!form) return;

81
src/views/sys/scrapy/createSpider.vue

@ -0,0 +1,81 @@
<template>
<BasicModal v-bind="$attrs" title="创建爬虫" @ok="createSpider">
<div class="m-4">
<BasicForm
:labelWidth="100"
:schemas="schemas"
:actionColOptions="{ span: 24 }"
:showActionButtonGroup="false"
/>
</div>
</BasicModal>
</template>
<script>
import { defineComponent } from 'vue';
import { BasicModal } from '/@/components/Modal';
import { BasicForm } from '/@/components/Form';
import { createSpiderApi } from '/@/api/sys/scrapy';
export default defineComponent({
name: "CreateSpiderModal",
components:{BasicModal, BasicForm},
emits:['success'],
setup(props,{emit}){
const schemas=[
{
field: 'field',
component: 'Input',
label: '爬虫名称',
colProps: {
span: 16,
},
componentProps: {
placeholder: '请输入爬虫名称',
onChange: handleCreateSpider,
},
},
];
const state = {
creatSpiderName:'',
}
async function createSpider(){
let res = await createSpiderApi({spiderName:state.creatSpiderName})
console.log(JSON.stringify(res));
emit('success',state.creatSpiderName);
}
//spiderName
let timerCreate = null
function debounceSpiderName(value){
timerCreate =window.setTimeout(function(){
state.creatSpiderName = value
},2000)
}
function handleCreateSpider(e) {
if(timerCreate) clearTimeout(Number(timerCreate))
debounceSpiderName(e.target.value)
}
return{
schemas,
createSpider,
}
}
})
</script>
<style scoped>
</style>

259
src/views/sys/scrapy/index.vue

@ -0,0 +1,259 @@
<template>
<div>
<div class="p-4">
<BasicTable
@register="refreshTable"
:pagination="{ pageSize: 10 }"
>
<template #toolbar>
<a-button type="primary" @click="getStatusList">获取状态列表</a-button>
<a-button type="primary" @click="handleCreate">新建爬虫</a-button>
</template>
<template #action="{ record }">
<TableAction
:actions="[
{
icon: 'clarity:note-edit-line',
onClick: handleEdit.bind(null, record),
ifShow:() => record.status === 3,
},
{
icon: 'ant-design:delete-outlined',
color: 'error',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
{
title:'运行爬虫',
icon: 'ant-design:caret-right-filled',
color:'success',
onClick:handleRunSpider.bind(null, record),
},
{
title:'停止运行',
icon: 'ant-design:stop-outlined',
color: 'warning',
onClick:handleStopSpider.bind(null,record),
}
]"
/>
</template>
</BasicTable>
</div>
<CreateSpiderModal @register='register' @success="handleCreateSuccess"></CreateSpiderModal>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
import { useModal } from '/@/components/Modal';
import { BasicTable, FormProps, useTable, TableAction } from "/@/components/Table";
import { getScrapyApi, runSpiderApi } from "/@/api/sys/scrapy";
import { scrapyColumns} from './product.data';
import CreateSpiderModal from './createSpider.vue'
export default defineComponent({
name: 'Spider',
components: { BasicTable, CreateSpiderModal,TableAction},
setup(){
const state =reactive({
spiderName:'',
status:'',
})
const detailSearchForm: FormProps = {
baseColProps: { lg: 6, md: 8 },
labelWidth: 90,
schemas: [
{
label: 'spiderName',
field: 'SpiderName',
component: 'InputSearch', //
componentProps: {
onChange: handleSpiderNameChange
}
},
{
label: 'status',
field: 'Status',
component: 'Select',
componentProps: {
options:[
{ label: '成功',value: 1,},
{ label: '失败', value: 2,},
{ label: '停止', value: 3,},
],
onChange: handleStatusChange
}
},
],
};
const [register, { openModal }] = useModal();
const [refreshTable, { reload, getDataSource}] = useTable({
title: '',
api: getScrapyApi,
columns: scrapyColumns,
formConfig: detailSearchForm,
useSearchForm: true,
showTableSetting: true,
bordered: true,
showIndexColumn: false,
searchInfo:{},
actionColumn: {
width: 120,
title: '操作',
dataIndex: 'action',
slots: { customRender: 'action' },
fixed: undefined,
},
});
/**
* @description:编辑爬虫
*/
function handleEdit(record:Recordable){
console.log(record);
}
/**
* @description:获取状态列表
*/
function getStatusList(){
const data = getDataSource()
console.log(data);
console.log(data.length);
for(let i = 0;i<data.length; i++){
if(data[i].status === 1 ){
data[i].statusChinese = '成功'
}else if(data[i].status ===2){
data[i].statusChinese = '失败'
}else if(data[i].status ===3){
data[i].statusChinese = '停止'
}
}
}
/**
* @description:删除爬虫
*/
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
reload();
getStatusList();
}
/**
* @description:运行爬虫
*/
async function handleRunSpider(record: Recordable){
console.log(record.id)
let res = await runSpiderApi({id:record.id})
console.log(JSON.stringify(res));
}
/**
* @description:停止运行爬虫
*/
function handleStopSpider(record: Recordable){
console.log(record);
}
/**
* @description:关闭创建爬虫弹窗
*/
function handleCreateSuccess(value) {
console.log(value);
reload();
getStatusList();
}
/**
* @description:获取status
*/
let timer: null| NodeJS.Timeout = null;
function handleStatusChange(e) {
//console.log(e.target.value);
if(timer) clearTimeout(Number(timer))
debounceSearch(e)
}
//status
function debounceSearch(value){
timer = setTimeout(function(){
state.status = value
console.log(state.status);
},1000)
}
/**
* @description:获取spiderName
*/
let time: null| NodeJS.Timeout = null;
function handleSpiderNameChange(e) {
if(time) clearTimeout(Number(time))
debounceSearch1(e.target.value)
}
//spiderName
function debounceSearch1(value){
time = setTimeout(function(){
state.spiderName = value
console.log(state.spiderName);
},1000)
}
/**
* @description:弹出创建爬虫弹窗
*/
function handleCreate() {
openModal(true, {
isUpdate: false,
});
}
return {
...toRefs(state),
register,
refreshTable,
handleCreateSuccess,
detailSearchForm,
handleCreate,
handleDelete,
handleEdit,
handleRunSpider,
getStatusList,
handleStopSpider,
};
},
});
</script>

55
src/views/sys/scrapy/product.data.ts

@ -0,0 +1,55 @@
import { BasicColumn } from '/@/components/Table';
export const scrapyColumns: BasicColumn[] = [
{
title: 'ID',
dataIndex: 'id',
fixed: 'left',
width: 180,
sorter: true,
},
{
title: '爬虫名称',
dataIndex: 'spiderName',
width: 150,
},
{
title: '状态',
dataIndex: 'statusChinese',
width: 80,
},
// {
// title: 'Event',
// dataIndex: 'event',
// width: 150,
//
// },
{
title: '运行周期',
dataIndex: 'duration',
width: 80,
},
// {
// title: '状态',
// dataIndex: 'statusChinese',
// width: 100,
//
// },
{
title: '创建时间',
width: 180,
sorter: true,
dataIndex: 'createdTime',
},
{
title: '更新时间',
width: 180,
sorter: true,
dataIndex: 'updatedTime',
},
];

11
types/axios.d.ts

@ -40,6 +40,17 @@ export interface Result<T = any> {
type: 'success' | 'error' | 'warning';
message: string;
result: T;
pages: number;
current: number;
size: number;
total: number;
}
export interface Records<T = any> {
code: number;
type: 'success' | 'error' | 'warning';
message: string;
records:T;
}
// multipart/form-data: upload file

2
vite.config.ts

@ -31,6 +31,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
const isBuild = command === 'build';
// @ts-ignore
return {
base: VITE_PUBLIC_PATH,
root,
@ -85,7 +86,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
define: {
__APP_INFO__: JSON.stringify(__APP_INFO__),
},
css: {
preprocessorOptions: {
less: {

12
yarn.lock

@ -8366,6 +8366,11 @@ sort-keys@^2.0.0:
dependencies:
is-plain-obj "^1.0.0"
sortablejs@1.14.0:
version "1.14.0"
resolved "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
sortablejs@^1.15.0:
version "1.15.0"
resolved "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a"
@ -9653,6 +9658,13 @@ vue@^3.2.45:
"@vue/server-renderer" "3.2.45"
"@vue/shared" "3.2.45"
vuedraggable@^4.1.0:
version "4.1.0"
resolved "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270"
integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==
dependencies:
sortablejs "1.14.0"
vxe-table-plugin-export-xlsx@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/vxe-table-plugin-export-xlsx/-/vxe-table-plugin-export-xlsx-3.0.4.tgz#35faabc0791b4e9aa516b78ea3fd45e67314afe4"

Loading…
Cancel
Save