Преглед на файлове

feat: 爬虫列表逻辑完善

cuiyalong преди 10 месеца
родител
ревизия
7a81c79201

+ 18 - 5
frontend/src/App.vue

@@ -27,12 +27,16 @@
           <el-icon><Setting /></el-icon>
           <span slot="title">系统设置</span> 
         </el-menu-item>
+        <el-menu-item index="/logout">
+          <el-icon><SwitchButton /></el-icon>
+          <span slot="title">退出</span> 
+        </el-menu-item>
       </el-menu>
     </el-aside>
     
     <el-container>
-      <el-header style="background-color: #F7F7F7; padding: 0;">
-        <h3><el-icon><Guide /></el-icon>剑鱼可视化爬虫开发平台 v1.0</h3>
+      <el-header class="header-container">
+        <h3 class="header-content"><el-icon class="header-icon"><Guide /></el-icon>剑鱼可视化爬虫开发平台 v1.0</h3>
       </el-header>
       <el-main>
         <router-view></router-view>
@@ -62,7 +66,7 @@ body {
 .el-header {
   align-items: left;
   height: 10vh;
- }
+}
  
 .sidebar-header {
   height: 60px;
@@ -78,6 +82,15 @@ body {
   border:none;
 }
 
- 
- 
+.header-container {
+  background-color: #F7F7F7;
+  padding: 0 12px;
+}
+.header-icon {
+  margin-right: 8px;
+}
+.header-content {
+  display: flex;
+  align-items: center;
+}
 </style>

+ 4 - 4
frontend/src/components/EditSpider.vue

@@ -6,8 +6,8 @@
                 <el-tag type="primary">{{ formData.site }} </el-tag>
                 <el-tag type="success">{{ formData.channel }} </el-tag>
                 <el-tag type="info">{{ formData.code }} </el-tag>
-                <el-tag type="warning">{{ formData.url }} </el-tag>
-                <el-tag type="danger">{{ formData.author }} </el-tag>
+                <el-tag type="warning">{{ formData.href }} </el-tag>
+                <el-tag type="danger">{{ formData.modifyuser }} </el-tag>
             </el-space>
         </div>
         <div class="space" />
@@ -107,9 +107,9 @@ const emit = defineEmits(['custom-event']);
 const formData = ref({
     site: '',
     channel: '',
-    url: '',
+    href: '',
     code: '',
-    author: '',
+    modifyuser: '',
 });
 
 const activeName = ref("first")

+ 21 - 6
frontend/src/router/index.js

@@ -2,23 +2,30 @@
 import { createRouter, createWebHashHistory } from 'vue-router';
 import store from '../store';
 
-
-import { ServerActionCheckLogin } from "../../wailsjs/go/main/App"
-
-import Home from "../views/Home.vue"
+// import Home from "../views/Home.vue"
+import CodeList from "../views/CodeList.vue"
 import Run from "../views/Run.vue"
 import Setting from "../views/Setting.vue"
 import Login from "../views/Login.vue"
+import Logout from '../views/Logout.vue'
 
 const routes = [
   {
     path: '/',
     redirect: '/code/list'
   },
+  // {
+  //   path: '/',
+  //   name: 'home',
+  //   component: Home,
+  //   meta: {
+  //     requiresAuth: true
+  //   }
+  // },
   {
     path: '/code/list',
     name: 'codeList',
-    component: Home,
+    component: CodeList,
     meta: {
       requiresAuth: true
     }
@@ -26,7 +33,7 @@ const routes = [
   {
     path: '/audit/list',
     name: 'auditList',
-    component: Home,
+    component: CodeList,
     meta: {
       requiresAuth: true
     }
@@ -47,6 +54,14 @@ const routes = [
       requiresAuth: true
     }
   },
+  {
+    path: '/logout',
+    name: 'logout',
+    component: Logout,
+    meta: {
+      requiresAuth: true
+    }
+  },
   {
     path: '/login',
     name: 'login',

+ 38 - 21
frontend/src/store/index.js

@@ -1,7 +1,7 @@
 // src/store/index.js
 import { createStore } from 'vuex';
 import { House, Setting, Help } from '@element-plus/icons-vue';
-import { ServerActionUserLogin, ServerActionCheckLogin } from '../../wailsjs/go/main/App'
+import { ServerActionUserLogin, ServerActionCheckLogin, ServerActionUserLogout } from '../../wailsjs/go/main/App'
 import rulesList from './modules/rulesList'
 
 const iconComponents = {
@@ -13,17 +13,20 @@ const iconComponents = {
 export default createStore({
     state: {
         isAuthenticated: false,
-        userInfo:{},
+        userInfo: {},
+        profile: {
+            nickname: '',
+        },
         menuConfig: {
             admin: [
-                { title: '首页', icon:iconComponents.house, path: '/' },
-                { title: '设置', icon:iconComponents.setting, path: '/setting' },
-                { title: '帮助', icon:iconComponents.help, path: '/help' },
+                { title: '首页', icon: iconComponents.house, path: '/' },
+                { title: '设置', icon: iconComponents.setting, path: '/setting' },
+                { title: '帮助', icon: iconComponents.help, path: '/help' },
                 // 更多管理员菜单项
             ],
             user: [
-                { title: '首页', icon:iconComponents.house, path: '/' },
-                { title: '帮助', icon:iconComponents.help, path: '/help' },
+                { title: '首页', icon: iconComponents.house, path: '/' },
+                { title: '帮助', icon: iconComponents.help, path: '/help' },
                 // 更多普通用户菜单项
             ],
         },
@@ -38,21 +41,33 @@ export default createStore({
     },
     actions: {
         async login({ commit }, credentials) {
-            const res = await ServerActionUserLogin({
-                username: credentials.username,
-                password: credentials.password
-            })
-            if (res) {
-                const userInfo = res.data
-                if (userInfo && Object.keys(userInfo).length > 0) {
-                    // 登录成功
-                    commit('SET_USER_INFO', userInfo);
-                    commit('SET_AUTHENTICATED', true);
-                } else {
-                    console.log('login error')
+            try {
+                const res = await ServerActionUserLogin({
+                    username: credentials.username,
+                    password: credentials.password
+                })
+                if (res) {
+                    const userInfo = res.data
+                    if (userInfo && Object.keys(userInfo).length > 0) {
+                        // 登录成功
+                        commit('SET_USER_INFO', userInfo);
+                        commit('SET_AUTHENTICATED', true);
+                        return {
+                            action: true,
+                            msg: '登录成功'
+                        }
+                    } else {
+                        const msg = res.msg || 'login: login error'
+                        // console.log(msg)
+                        return {
+                            action: true,
+                            msg
+                        }
+                    }
                 }
+            } catch (error) {
+                return 
             }
-            return res
         },
         async checkLogin({ commit }) {
             const res = await ServerActionCheckLogin()
@@ -67,8 +82,10 @@ export default createStore({
                 }
             }
         },
-        logout({ commit }) {
+        async logout({ commit }) {
+            await ServerActionUserLogout()
             commit('SET_AUTHENTICATED', false);
+            return true
         },
     },
     getters: {

+ 13 - 6
frontend/src/store/modules/rulesList.js

@@ -1,4 +1,4 @@
-import { ServerActionCodeList } from '../../../wailsjs/go/main/App'
+import { ServerActionCodeList, ServerActionGetModifyUsers } from '../../../wailsjs/go/main/App'
 
 // 爬虫列表模块(局部模块)
 export default {
@@ -23,15 +23,22 @@ export default {
   },
   actions: {
     // 定义actions,用于异步修改状态
+    // 获取爬虫列表
     async getCodeList({ commit }, payload) {
+      const { pageSize, pageNum } = payload
       const r = await ServerActionCodeList({
-          modifyuser: payload.modifyuser, // 维护人
-          state: payload.state, // 爬虫状态
-          search: payload.search, // 搜索内容
-          start: 0,
-          limit: 10
+        modifyuser: payload.modifyuser, // 维护人
+        state: payload.state, // 爬虫状态
+        search: payload.search, // 搜索内容
+        start: (pageNum - 1) * pageSize,
+        limit: pageSize
       })
       return r
+    },
+    // 获取维护人列表
+    async getModifyUserList() {
+      const r = await ServerActionGetModifyUsers()
+      return r
     }
   },
   getters: {}

+ 334 - 0
frontend/src/views/CodeList.vue

@@ -0,0 +1,334 @@
+<template>
+    <Navigator pageTitle="爬虫列表"></Navigator>
+    <div class="space"></div>
+    <el-card>
+        <el-header>
+            <div class="action-bar-container">
+                <el-space class="action-bar-item-container action-bar-action-left">
+                    <el-button size="small" type="success" @click="resetFilterAndRefreshTableList">
+                        刷新
+                    </el-button>
+                </el-space>
+                <el-space class="action-bar-item-container action-bar-action-right">
+                    <div class="action-bar-item">
+                        <span class="action-bar-name">维护人:</span>
+                        <el-select v-model="filters.modifyuser" placeholder="维护人" style="width: 140px" @change="onModifyUserSelectChange">
+                            <el-option
+                                v-for="item in filterConfig.modifyUserList"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                                
+                            />
+                        </el-select>
+                    </div>
+                    <div class="action-bar-item">
+                        <span class="action-bar-name">爬虫状态:</span>
+                        <el-select v-model="filters.state" placeholder="爬虫状态" style="width: 140px" @change="onStateSelectChange">
+                            <el-option
+                                v-for="item in filterConfig.stateOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            />
+                        </el-select>
+                    </div>
+                    <div class="action-bar-item">
+                        <el-input v-model="filters.search" placeholder="按照爬虫代码搜索" @keydown.enter="onInputSearch" />
+                    </div>
+                </el-space>
+            </div>
+        </el-header>
+        <el-main>
+            <el-table ref="spiderTable" :data="listState.list" stripe :row-style="getRowStyle" :loading="listState.loading">
+                <el-table-column prop="code" label="代码" show-overflow-tooltip></el-table-column>
+                <el-table-column prop="site" label="网站" show-overflow-tooltip></el-table-column>
+                <el-table-column prop="channel" label="栏目" show-overflow-tooltip></el-table-column>
+                <el-table-column prop="href" label="栏目地址" show-overflow-tooltip></el-table-column>
+                <el-table-column prop="modifyuser" label="维护人" width="80" show-overflow-tooltip></el-table-column>
+                <el-table-column label="操作" width="200">
+                    <template #default="scope">
+                        <el-button size="small" type="success" @click="tableEvents.handleDataTag(scope.$index, scope.row)">
+                            标注
+                        </el-button>
+                        <el-button size="small" type="warning" @click="tableEvents.handleEdit(scope.$index, scope.row)">
+                            编辑
+                        </el-button>
+                        <el-button size="small" type="danger" @click="tableEvents.handleDebug(scope.$index, scope.row)">
+                            调试
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <div class="space"></div>
+            <div class="pagination-container">
+                <el-pagination align="right" @size-change="handleSizeChange" @current-change="handleCurrentChange"
+                    :current-page="listState.pageNum" :page-sizes="[10, 20, 30, 40]" :page-size="listState.pageSize"
+                    layout="total, prev, pager, next, jumper" :total="listState.total" hide-on-single-page>
+                </el-pagination>
+            </div>
+        </el-main>
+    </el-card>
+    <EditSpider ref="editSpiderDialog" @custom-event="dialogEvents.editSpiderConfigSaveEvent" />
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted, reactive } from 'vue'
+import { useRouter } from 'vue-router';
+import { useStore } from 'vuex';
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { BrowserOpenURL, EventsOn } from "../../wailsjs/runtime"
+import { SaveOrUpdateSpiderConfig } from "../../wailsjs/go/main/App"
+import { SwitchSpiderConfig } from "../../wailsjs/go/main/App"
+import Navigator from "../components/Navigator.vue"
+import EditSpider from "../components/EditSpider.vue"
+
+const router = useRouter();
+const store = useStore();
+const spiderTable = ref(null)
+
+const filterConfig = reactive({
+    // 爬虫状态
+    stateOptions: [
+        {
+            label: '全部',
+            value: -1,
+        },
+        {
+            label: '待完成',
+            value: 0,
+        },
+        {
+            label: '待审核',
+            value: 1,
+        },
+        {
+            label: '未通过',
+            value: 2,
+        },
+        {
+            label: '已通过',
+            value: 3,
+        },
+        {
+            label: '已上线',
+            value: 11,
+        },
+        {
+            label: '无法标注',
+            value: 12,
+        },
+    ],
+    modifyUserList: [
+        // {
+        //     label: '全部',
+        //     value: ''
+        // }
+    ]
+})
+
+// 选择器数据
+const filters = reactive({
+    search: '',
+    state: -1,
+    modifyuser: '',
+})
+// 选择器数据(用来重置)
+const defaultFilters = {
+    search: '',
+    state: -1,
+    modifyuser: '',
+}
+
+// 列表数据
+const listState = reactive({
+    loaded: false,
+    loading: false,
+    pageNum: 1, // 页码
+    pageSize: 10, // 每页多少条
+    total: 0, // 返回的总数据条数
+    list: [ // 数据列表
+        // {
+        //     code: '爬虫代码', 
+        //     site: '网站',
+        //     channel: '栏目',
+        //     href: '栏目地址',
+        //     modifyuser: '维护人',
+        //     _id: 'asfasf',
+        // },
+    ],
+})
+// 列表默认数据(用来重置)
+const defaultListState = {
+    loaded: false,
+    loading: false,
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    list: [],
+}
+
+const editSpiderDialog = ref(null)
+
+const dialogEvents = {
+    editSpiderConfigSaveEvent: function (data) {
+        console.log("change data:", data)
+        SaveOrUpdateSpiderConfig(data).then(result => {
+            ElMessage({
+                message: `成功更新爬虫 ${data.site} /${data.channel}/${data.code}`,
+                showClose: true,
+                duration: 3000,
+            });
+            //表格数据更新
+            listState.list.forEach((v, i) => {
+                if (v.code == data.code) v = data
+            })
+            //更新当前选择
+            SwitchSpiderConfig(data.code).then(result => { })
+        })
+    },
+}
+
+const tableEvents = {
+    handleDataTag(index, row) {
+        // 自定义关闭时间
+        ElMessage({
+            message: `${row.site} ${row.channel} ${row.href}`,
+            showClose: true,
+            duration: 3000,
+        });
+        BrowserOpenURL(row.href)
+    },
+    handleEdit: (index, row) => {
+        ElMessage({
+            message: `${row.site} ${row.channel} ${row.href}`,
+            showClose: true,
+            duration: 3000,
+        });
+        editSpiderDialog.value.dialogVisible = true
+        editSpiderDialog.value.formData = row
+    },
+    handleDebug(index, row) {
+        router.push({
+            path: '/run'
+        });
+    }
+}
+
+const getRowStyle = ({ row }) => {
+    return row.selected ? { backgroundColor: '#F7F7F7' } : {};
+};
+
+// 获取列表数据
+const getTableList = async () => {
+    const r = await store.dispatch('rulesList/getCodeList', {
+        modifyuser: filters.modifyuser, // 维护人
+        state: filters.state, // 爬虫状态
+        search: filters.search, // 搜索内容
+        pageSize: listState.pageSize,
+        pageNum: listState.pageNum
+    });
+    const { data, code } = r
+    if (data) {
+        listState.total = data.total || 0
+        if (Array.isArray(data.list)) {
+            listState.list = data.list || []
+        }
+    }
+}
+
+// 重置列表数据
+const resetListState = () => {
+    Object.assign(listState, defaultListState)
+}
+// 重置选择器数据
+const resetFilterState = () => {
+    Object.assign(filters, defaultFilters)
+}
+
+// 刷新列表(不重置选择器)
+const refreshTableList = () => {
+    resetListState()
+    getTableList()
+}
+// 刷新列表(并重置选择器)
+const resetFilterAndRefreshTableList = () => {
+    resetFilterState()
+    resetListState()
+    getTableList()
+}
+
+getTableList()
+
+const handleSizeChange = (val) => {
+    listState.pageSize = val;
+    listState.pageNum = 1;
+};
+
+const handleCurrentChange = (val) => {
+    listState.pageNum = val;
+    getTableList()
+};
+
+const getModifyUserList = async () => {
+    const r = await store.dispatch('rulesList/getModifyUserList')
+    const { data, code } = r
+    if (data) {
+        if (Array.isArray(data.list)) {
+            const arr = [{ s_name: '全部', value: '' }]
+            const reqArr = data.list.map(r => {
+                return {
+                    name: r.s_name,
+                    value: r.s_name,
+                }
+            })
+            filterConfig.modifyUserList = arr.concat(reqArr)
+        }
+    }
+}
+getModifyUserList()
+
+const onModifyUserSelectChange = () => {
+    refreshTableList()
+}
+const onStateSelectChange = () => {
+    refreshTableList()
+}
+const onInputSearch = () => {
+    refreshTableList()
+}
+
+
+//Wails事件绑定
+EventsOn("spiderConfigChange", data => {
+    listState.list.forEach((v, i) => {
+        if (v.code == data.code) {
+            let rowData = { ...data }
+            listState.list[i] = rowData
+            spiderTable.value.toggleRowSelection(listState.list[i], true);
+        }
+    })
+})
+</script>
+
+<style scoped>
+.pagination-container {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+}
+.action-bar-container {
+    display: flex;
+    justify-content: space-between;
+}
+.action-bar-item-container {
+    
+}
+.action-bar-item {
+    display: flex;
+    align-items: center;
+}
+.action-bar-name {
+    font-size: 14px;
+}
+</style>

+ 11 - 4
frontend/src/views/Login.vue

@@ -10,7 +10,7 @@
                         <el-input v-model="form.username" placeholder="请输入用户名" />
                     </el-form-item>
                     <el-form-item label="密码">
-                        <el-input type="password" v-model="form.password" placeholder="请输入密码"></el-input>
+                        <el-input type="password" v-model="form.password" placeholder="请输入密码" @keydown.enter="doLogin"></el-input>
                     </el-form-item>
                     <el-form-item style="text-align: center;">
                         <el-button text>
@@ -31,7 +31,7 @@ import { ref } from 'vue';
 import Navigator from '../components/Navigator.vue'
 import { useStore } from 'vuex';
 import { useRouter } from 'vue-router';
-// import { GetLoginState, PutLoginState } from "../../wailsjs/go/main/App"
+import { ElMessage } from 'element-plus'
 
 const store = useStore();
 const router = useRouter();
@@ -40,12 +40,19 @@ const form = ref({})
 //
 const doLogin = async () => {
     //TODO 这里需要调用后台方法实现登录
-    const r = await store.dispatch('login', {...form.value});
+    const { msg } = await store.dispatch('login', {...form.value});
     if (store.state.isAuthenticated) {
         // 登录成功。跳转首页
         router.replace({ name: 'codeList' })
+        ElMessage({
+            message: msg,
+            type: 'success',
+        })
     } else {
-        // 登录失败,提示
+        ElMessage({
+            message: msg,
+            type: 'error',
+        })
     }
 }
 </script>

+ 14 - 0
frontend/src/views/Logout.vue

@@ -0,0 +1,14 @@
+<script setup>
+import { useStore } from 'vuex';
+import { useRouter } from 'vue-router';
+
+const store = useStore();
+const router = useRouter();
+const doLogout = async () => {
+    const res = await store.dispatch('logout')
+    if (res) {
+        router.replace({ name: 'login' })
+    }
+}
+doLogout()
+</script>

+ 8 - 0
frontend/wailsjs/go/main/App.d.ts

@@ -28,10 +28,18 @@ export function SelectSaveFilePath():Promise<string>;
 
 export function ServerActionCheckLogin():Promise<{[key: string]: any}>;
 
+export function ServerActionClaimCodes():Promise<{[key: string]: any}>;
+
 export function ServerActionCodeList(arg1:{[key: string]: any}):Promise<{[key: string]: any}>;
 
+export function ServerActionGetModifyUsers():Promise<{[key: string]: any}>;
+
+export function ServerActionUpdateCode(arg1:{[key: string]: any}):Promise<{[key: string]: any}>;
+
 export function ServerActionUserLogin(arg1:{[key: string]: any}):Promise<{[key: string]: any}>;
 
+export function ServerActionUserLogout():Promise<{[key: string]: any}>;
+
 export function StopDebugSpider():Promise<string>;
 
 export function SwitchSpiderConfig(arg1:string):Promise<string>;

+ 16 - 0
frontend/wailsjs/go/main/App.js

@@ -54,14 +54,30 @@ export function ServerActionCheckLogin() {
   return window['go']['main']['App']['ServerActionCheckLogin']();
 }
 
+export function ServerActionClaimCodes() {
+  return window['go']['main']['App']['ServerActionClaimCodes']();
+}
+
 export function ServerActionCodeList(arg1) {
   return window['go']['main']['App']['ServerActionCodeList'](arg1);
 }
 
+export function ServerActionGetModifyUsers() {
+  return window['go']['main']['App']['ServerActionGetModifyUsers']();
+}
+
+export function ServerActionUpdateCode(arg1) {
+  return window['go']['main']['App']['ServerActionUpdateCode'](arg1);
+}
+
 export function ServerActionUserLogin(arg1) {
   return window['go']['main']['App']['ServerActionUserLogin'](arg1);
 }
 
+export function ServerActionUserLogout() {
+  return window['go']['main']['App']['ServerActionUserLogout']();
+}
+
 export function StopDebugSpider() {
   return window['go']['main']['App']['StopDebugSpider']();
 }