Эх сурвалжийг харах

feat: 新增审核列表页面

cuiyalong 10 сар өмнө
parent
commit
ef67381ff6

+ 1 - 1
frontend/src/App.vue

@@ -65,7 +65,7 @@ function toggleSidebar() {
 toggleSidebar()
 
 const menuList = computed(() => store.getters.getCurrentMenu)
-const userName = computed(() => store.state.userInfo.s_name)
+const userName = computed(() => store.state.userInfo.s_fullname)
 const userRole = computed(() => store.getters.userRole)
 const showLogoutModule = computed(() => store.state.isAuthenticated)
 const doLogout = () => {

+ 15 - 2
frontend/src/composables/filter-options.js

@@ -1,4 +1,4 @@
-import { spiderStateOptions, spiderClaimOptions } from '../data'
+import { spiderStateOptions, spiderClaimOptions, reviewSpiderStateOptions } from '../data'
 import { useStore } from 'vuex'
 
 function checkFilterPower(list = [], role = '') {
@@ -15,7 +15,8 @@ function checkFilterPower(list = [], role = '') {
   })
 }
 
-export default function useCodeListFiltersWithRole() {
+// 爬虫列表筛选备选项权限控制
+export function useCodeListFiltersWithRole() {
   const store = useStore()
   const role = store.getters.userRole
   const stateOptions = checkFilterPower(spiderStateOptions, role)
@@ -25,3 +26,15 @@ export default function useCodeListFiltersWithRole() {
     claimOptions,
   }
 }
+
+// 审核列表筛选备选项权限控制
+export function useReviewListFiltersWithRole() {
+  const store = useStore()
+  const role = store.getters.userRole
+  const stateOptions = checkFilterPower(reviewSpiderStateOptions, role)
+  const claimOptions = checkFilterPower(spiderClaimOptions, role)
+  return {
+    stateOptions,
+    claimOptions,
+  }
+}

+ 31 - 1
frontend/src/data/filters.js

@@ -1,5 +1,5 @@
 import { USER_ROLE_ADMIN, USER_ROLE_DEVELOPER, USER_ROLE_REVIEWER } from './user'
-// codeList备选项
+// codeList爬虫列表爬虫状态备选项
 export const spiderStateOptions = [
   {
     label: '全部',
@@ -31,6 +31,36 @@ export const spiderStateOptions = [
   },
 ]
 
+// codeList审核列表爬虫状态备选项
+export const reviewSpiderStateOptions = [
+  {
+    label: '全部',
+    value: -1,
+    role: [USER_ROLE_ADMIN],
+  },
+  {
+    label: '待审核',
+    value: 1,
+    role: [USER_ROLE_ADMIN, USER_ROLE_REVIEWER],
+  },
+  {
+    label: '未通过',
+    value: 2,
+    role: [USER_ROLE_ADMIN, USER_ROLE_REVIEWER],
+  },
+  {
+    label: '已通过',
+    value: 3,
+    role: [USER_ROLE_ADMIN, USER_ROLE_REVIEWER],
+  },
+  {
+    label: '已上线',
+    value: 11,
+    role: [USER_ROLE_ADMIN],
+  },
+]
+
+// codeList爬虫给列表认领状态
 export const spiderClaimOptions = [
   {
     label: '全部',

+ 4 - 7
frontend/src/router/index.js

@@ -4,15 +4,12 @@ import store from '../store';
 
 // import Home from "../views/Home.vue"
 import CodeList from "../views/CodeList.vue"
+import ReviewList from "../views/ReviewList.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',
@@ -30,9 +27,9 @@ const routes = [
     }
   },
   {
-    path: '/audit/list',
-    name: 'auditList',
-    component: CodeList,
+    path: '/review/list',
+    name: 'reviewList',
+    component: ReviewList,
     meta: {
       requiresAuth: true
     }

+ 5 - 7
frontend/src/store/index.js

@@ -24,20 +24,18 @@ export default createStore({
             // 管理员菜单
             [USER_ROLE_ADMIN]: [
                 { title: '爬虫列表', icon: 'Guide', path: '/code/list' },
-                { title: '审核列表', icon: 'Setting', path: '/audit/list' },
-                { title: '系统设置', icon: 'Help', path: '/setting' },
+                { title: '审核列表', icon: 'Setting', path: '/review/list' },
+                // { title: '系统设置', icon: 'Help', path: '/setting' },
             ],
             // 开发者菜单
             [USER_ROLE_DEVELOPER]: [
                 { title: '爬虫列表', icon: 'Guide', path: '/code/list' },
-                { title: '审核列表', icon: 'Setting', path: '/audit/list' },
-                { title: '系统设置', icon: 'Help', path: '/setting' },
+                // { title: '系统设置', icon: 'Help', path: '/setting' },
             ],
             // 审核人员菜单
             [USER_ROLE_REVIEWER]: [
-                { title: '爬虫列表', icon: 'Guide', path: '/code/list' },
-                { title: '审核列表', icon: 'Setting', path: '/audit/list' },
-                { title: '系统设置', icon: 'Help', path: '/setting' },
+                { title: '审核列表', icon: 'Setting', path: '/review/list' },
+                // { title: '系统设置', icon: 'Help', path: '/setting' },
             ],
         },
     },

+ 1 - 0
frontend/src/store/modules/rulesList.js

@@ -37,6 +37,7 @@ export default {
         state: payload.state, // 爬虫状态
         claimtype: payload.claimtype, // 认领状态
         search: payload.search, // 搜索内容
+        page: payload.pageType,
         start: (pageNum - 1) * pageSize,
         limit: pageSize
       })

+ 2 - 1
frontend/src/views/CodeList.vue

@@ -129,7 +129,7 @@ import Breadcrumb from "../components/Breadcrumb.vue"
 import EditSpider from "../components/spider/EditSpider.vue"
 import RunSpiderDialog from "../components/spider/RunSpiderDialog.vue"
 import VerifySpider from "../components/spider/VerifySpider.vue"
-import useCodeListFiltersWithRole from '../composables/filter-options'
+import { useCodeListFiltersWithRole } from '../composables/filter-options'
 import { USER_ROLE_ADMIN, USER_ROLE_DEVELOPER, USER_ROLE_REVIEWER } from '../data/user'
 import { Refresh, Search, Box } from '@element-plus/icons-vue'
 
@@ -287,6 +287,7 @@ async function getTableList() {
             state: filters.state, // 爬虫状态
             search: filters.search, // 搜索内容
             claimtype: filters.claimtype,
+            pageType: 'codelist',
             pageSize: listState.pageSize,
             pageNum: listState.pageNum
         });

+ 13 - 5
frontend/src/views/Login.vue

@@ -60,11 +60,19 @@ const doLogin = async () => {
         loading.value = false
         if (store.state.isAuthenticated) {
             // 登录成功。跳转首页
-            router.replace({ name: 'codeList' })
-            ElMessage({
-                message: msg,
-                type: 'success',
-            })
+            const path = store.getters.getCurrentMenu[0]?.path
+            if (path) {
+                router.replace({ path: path })
+                ElMessage({
+                    message: msg,
+                    type: 'success',
+                })
+            } else {
+                ElMessage({
+                    message: '未找到配置路由',
+                    type: 'error',
+                })
+            }
         } else {
             ElMessage({
                 message: msg,

+ 685 - 0
frontend/src/views/ReviewList.vue

@@ -0,0 +1,685 @@
+<template>
+    <Breadcrumb pageTitle="审核列表"></Breadcrumb>
+    <div class="space"></div>
+    <el-card v-loading="loading">
+        <el-header>
+            <div class="action-bar-container">
+                <el-space class="action-bar-item-container action-bar-action-left">
+                    <el-button-group class="ml-4">
+                        <el-button type="primary" :icon="Refresh" @click="resetFilterAndRefreshTableList">刷新</el-button>
+                    </el-button-group>
+                </el-space>
+                <el-space class="action-bar-item-container action-bar-action-right">
+                    <div class="action-bar-item" v-if="showModifyUserFilter">
+                        <span class="action-bar-name">维护人:</span>
+                        <el-select v-model="filters.modifyuser" style="width: 110px" @change="onModifyUserSelectChange" :disabled="modifyUserSelectDisabled">
+                            <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: 110px" @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">
+                        <span class="action-bar-name">认领状态:</span>
+                        <el-select v-model="filters.claimtype" placeholder="认领状态" style="width: 110px" @change="onClaimSelectChange">
+                            <el-option
+                                v-for="item in filterConfig.claimOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                                :disabled="item.disabled"
+                            />
+                        </el-select>
+                    </div> -->
+                    <div class="action-bar-item">
+                        <el-input v-model="filters.search" placeholder="按照爬虫代码搜索" @keydown.enter="onInputSearch" />
+                    </div>
+                    <div class="action-bar-item">
+                        <el-button type="primary" :icon="Search" @click="onInputSearch">搜索</el-button>
+                    </div>
+                </el-space>
+            </div>
+        </el-header>
+        <el-main>
+            <el-table ref="spiderTable" :data="listState.list" stripe :row-style="getRowStyle" v-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="stateText" label="爬虫状态" show-overflow-tooltip></el-table-column>
+                <el-table-column prop="claimText" 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="160" align="center">
+                    <template #default="scope">
+                  <!-- <el-tooltip content="标注" placement="top">
+                            <el-button size="small" :class="{ active: scope.row._action_clicked_mark }" @click="tableEvents.handleDataTag(scope.row)">
+                                <el-icon><Link /></el-icon>
+                            </el-button>
+                        </el-tooltip>-->
+                        <el-tooltip content="编辑" placement="top">
+                            <el-button size="small" :class="{ active: scope.row._action_clicked_edit }" @click="tableEvents.handleEdit(scope.$index, scope.row)">
+                                <el-icon><Edit /></el-icon>
+                            </el-button>
+                        </el-tooltip>
+                        <el-tooltip content="调试" placement="top">
+                            <el-button size="small" :class="{ active: scope.row._action_clicked_debug }" @click="tableEvents.handleDebug(scope.$index, scope.row)">
+                                <el-icon><SetUp /></el-icon>
+                            </el-button>
+                        </el-tooltip>
+                        <el-tooltip content="验证" placement="top">
+                            <el-button size="small" :class="{ active: scope.row._action_clicked_verify }" @click="tableEvents.handleVerify(scope.$index, scope.row)">
+                                <el-icon><Aim /></el-icon>
+                            </el-button>
+                        </el-tooltip>
+                    </template>
+                </el-table-column>
+                <el-table-column label="功能" width="120" align="center">
+                    <template #default="scope">
+                        <el-tooltip content="提交" placement="top" v-if="showListDevelopeButton">
+                            <!-- 爬虫状态是待完成,才可点击提交 -->
+                            <el-button size="small" :disabled="scope.row.state !== 0" :class="{ active: scope.row._action_clicked_submit }" @click="tableEvents.handleSubmit(scope.$index, scope.row)">
+                                <el-icon><Promotion /></el-icon>
+                            </el-button>
+                        </el-tooltip>
+                        <el-tooltip content="回退" placement="top" v-if="showListRollbackButton">
+                            <!-- 爬虫状态是待完成,才可点击回退 -->
+                            <el-button size="small" :disabled="scope.row.state !== 0" :class="{ active: scope.row._action_clicked_rollback }" @click="tableEvents.handleRollback(scope.$index, scope.row)">
+                                <el-icon><DArrowLeft /></el-icon>
+                            </el-button>
+                        </el-tooltip>
+                    </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, sizes, prev, pager, next, jumper" :total="listState.total">
+                </el-pagination>
+            </div>
+        </el-main>
+    </el-card>
+    <EditSpider ref="editSpiderDialog" @custom-event="dialogEvents.editSpiderConfigSaveEvent" @data-tag="editDialogMarkClick($event)" />
+    <RunSpiderDialog ref="runSpiderDialog" />
+    <VerifySpider ref="verifySpiderDialog" />
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted, reactive, watch } from 'vue'
+import { useRouter } from 'vue-router';
+import { useStore } from 'vuex';
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { BrowserOpenURL, EventsOn } from "../../wailsjs/runtime"
+import { VerifySpiderConfig, ServerActionUpdateCodeState, ServerActionCurrentOpenTab } from "../../wailsjs/go/main/App"
+import Breadcrumb from "../components/Breadcrumb.vue"
+import EditSpider from "../components/spider/EditSpider.vue"
+import RunSpiderDialog from "../components/spider/RunSpiderDialog.vue"
+import VerifySpider from "../components/spider/VerifySpider.vue"
+import { useReviewListFiltersWithRole } from '../composables/filter-options'
+import { USER_ROLE_ADMIN, USER_ROLE_DEVELOPER, USER_ROLE_REVIEWER } from '../data/user'
+import { Refresh, Search, Box } from '@element-plus/icons-vue'
+
+const router = useRouter();
+const store = useStore();
+const spiderTable = ref(null)
+
+const { stateOptions, claimOptions } = useReviewListFiltersWithRole()
+
+const loading = ref(false)
+
+const filterConfig = reactive({
+    // 爬虫状态备选项
+    stateOptions: stateOptions,
+    // 认领状态备选项
+    claimOptions: claimOptions,
+    // 维护人备选项
+    modifyUserList: [
+        {
+            label: '全部',
+            value: '-1',
+        }
+    ]
+})
+
+
+// 选择器数据(用来重置)
+const defaultFilters = {
+    // 搜索
+    search: '',
+    // 爬虫状态(审核员默认待审核1,管理员默认已通过3)
+    state: -1,
+    // 维护人
+    modifyuser: '-1',
+    // 认领状态
+    claimtype: -1,
+}
+// 选择器数据
+const filters = reactive({
+    // 搜索
+    search: '',
+    // 爬虫状态(审核员默认待审核1,管理员默认已通过3)
+    state: 1,
+    // 维护人
+    modifyuser: '-1',
+    // 认领状态
+    claimtype: -1,
+})
+
+// 列表数据
+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: [],
+}
+
+// 当前编辑的row的数据
+const currentEditRow = ref({})
+// 上一个点击的row的数据
+const prevClickedRow = ref({})
+
+// 用户身份标识
+const userRole = computed(() => store.getters.userRole)
+// 是否展示维护人模块(管理员和审核员展示)
+const showModifyUserFilter = computed(() => [USER_ROLE_ADMIN, USER_ROLE_REVIEWER].includes(userRole.value))
+// 是否展示提交按钮(仅开发人员展示)
+const showListDevelopeButton = computed(() => [USER_ROLE_DEVELOPER].includes(userRole.value))
+// 是否展示回退按钮(仅开发人员展示)
+const showListRollbackButton = computed(() => [USER_ROLE_DEVELOPER].includes(userRole.value))
+
+// 实现待认领不能和维护人同时筛选
+// 是否禁用维护人模块(待认领筛选下,禁用维护人筛选模块)
+const modifyUserSelectDisabled = computed(() => filters.claimtype === 0)
+// 是否禁用待认领筛选(维护人不为全部,则禁用待认领的选中)
+const stopWatch = watch(() => filters.modifyuser, (n) => {
+    const target = filterConfig.claimOptions.find(i => i.value === 0)
+    if (target) {
+        if (n === defaultFilters.modifyuser) {
+            // 解除禁用待认领
+            target.disabled = false
+        } else {
+            // 禁用待认领
+            target.disabled = true
+        }
+    } else {
+        stopWatch()
+    }
+})
+
+const changeFilterDefaultValue = () => {
+    // 根据用户身份
+    // 爬虫状态(审核员默认待审核1,管理员默认已通过3)
+    if ([USER_ROLE_ADMIN].includes(userRole.value)) {
+        // 管理员
+        filters.state = 3
+    }
+}
+changeFilterDefaultValue()
+
+const editSpiderDialog = ref(null)
+const runSpiderDialog = ref(null)
+const verifySpiderDialog = ref(null)
+
+// 取消prevClicked的高亮
+const cancelOtherHighlight = () => {
+    for (const key in prevClickedRow.value) {
+        if (key.includes('_action_clicked')) {
+            prevClickedRow.value[key] = false
+        }
+    }
+}
+// 页面按钮点击高亮
+const onlyClickHighlight = (row, key) => {
+    cancelOtherHighlight()
+    row[key] = true
+    prevClickedRow.value = row
+}
+
+const getRowStyle = ({ row }) => {
+    return row.selected ? { backgroundColor: '#F7F7F7' } : {};
+};
+
+// 计算爬虫状态
+const calcStateText = (state) => {
+    const target = filterConfig.stateOptions.find(r => r.value === state)
+    if (target) {
+        return target.label
+    } else {
+        return ''
+    }
+}
+// 计算认领状态
+const calcClaimText = (state) => {
+    const target = filterConfig.claimOptions.find(r => r.value === state)
+    if (target) {
+        return target.label
+    } else {
+        return ''
+    }
+}
+// 获取列表数据
+async function getTableList() {
+    listState.loading = true
+    try {
+        const r = await store.dispatch('rulesList/getCodeList', {
+            modifyuser: filters.modifyuser, // 维护人
+            state: filters.state, // 爬虫状态
+            search: filters.search, // 搜索内容
+            claimtype: filters.claimtype,
+            pageType: 'reviewlist',
+            pageSize: listState.pageSize,
+            pageNum: listState.pageNum
+        });
+        const { data, err, msg } = r
+        if (data) {
+            listState.total = data.total || 0
+            if (Array.isArray(data.list)) {
+                const sList = data.list.map(t => {
+                    return {
+                        ...t,
+                        stateText: calcStateText(t.state),
+                        claimText: calcClaimText(t.claimtype),
+                        // 操作按钮是否点击过
+                        _action_clicked_mark: false,
+                        _action_clicked_edit: false,
+                        _action_clicked_debug: false,
+                        _action_clicked_verify: false,
+                        _action_clicked_submit: false,
+                        _action_clicked_rollback: false,
+                    }
+                })
+
+                listState.list = sList || []
+            }
+        }
+    } catch (error) {
+        listState.loaded = true
+    } finally {
+        listState.loading = false
+    }
+}
+
+// 重置列表数据
+const resetListState = () => {
+    Object.assign(listState, defaultListState)
+}
+// 重置选择器数据
+const resetFilterState = () => {
+    Object.assign(filters, defaultFilters)
+}
+
+// 刷新列表(不重置选择器)
+function refreshTableList() {
+    resetListState()
+    getTableList()
+}
+// 刷新列表(并重置选择器)
+function resetFilterAndRefreshTableList() {
+    resetFilterState()
+    refreshTableList()
+}
+
+getTableList()
+
+const handleSizeChange = (val) => {
+    listState.pageSize = val;
+    listState.pageNum = 1;
+    getTableList()
+};
+
+const handleCurrentChange = (val) => {
+    listState.pageNum = val;
+    getTableList()
+};
+
+// 获取维护人劣列表
+const getModifyUserList = async () => {
+    const r = await store.dispatch('rulesList/getModifyUserList')
+    const { data, err, msg } = r
+    if (data) {
+        if (Array.isArray(data.list)) {
+            const arr = filterConfig.modifyUserList
+            const reqArr = data.list.map(r => {
+                return {
+                    label: r.s_name,
+                    value: r.s_name,
+                }
+            })
+            filterConfig.modifyUserList = arr.concat(reqArr)
+        }
+    }
+}
+getModifyUserList()
+
+const onModifyUserSelectChange = () => {
+    refreshTableList()
+}
+const onStateSelectChange = () => {
+    refreshTableList()
+}
+const onInputSearch = () => {
+    refreshTableList()
+}
+const onClaimSelectChange = () => {
+    refreshTableList()
+}
+
+// 保存完成后(不刷新优化),同步更新表单内的数据
+const refreshRowData = (code, payload) => {
+    const target = listState.list.find(item => item.code === code)
+    if (target) {
+        if (target.cssmark) {
+            Object.assign(target.cssmark, payload)
+        } else {
+            target.cssmark = payload            
+        }
+    }
+}
+// 更新编辑弹窗数据
+const refreshAndAsyncEditDialog = (key, value) => {
+    editSpiderDialog.value.refreshPageData(key, value)
+}
+
+// 打开编辑弹窗(如果传数据了,则恢复数据)
+const openEditDialog = (row) => {
+    editSpiderDialog.value.dialogVisible = true
+    if (row) {
+        editSpiderDialog.value.setPageData({
+            ...row,
+            ...row.cssmark,
+        })
+    }
+}
+
+
+const dialogEvents = {
+    editSpiderConfigSaveEvent: async function (data) {
+        // 整理数据结构
+        // [{ query: {code: 'code'}, set: {} }, {query:{},set:{}}]
+        const rowData = data._originData
+        const payload = data.value
+        const code = rowData.code
+        const updateRule = [
+            {
+                query: { code: code },
+                set: {
+                    cssmark: payload,
+                }
+            }
+        ]
+        const params = {
+            stype: 'save',
+            update: updateRule
+        }
+        console.log("change data:", data, params)
+        try {
+            const r = await store.dispatch('rulesList/editCodeItem', params)
+            const { msg, err } = r
+            if (err === 1) {
+                refreshRowData(code, payload)
+                ElMessage({
+                    message: msg || '保存成功',
+                    type: 'success',
+                    duration: 3000,
+                })
+                // getTableList()
+            } else {
+                ElMessage({
+                    message: msg || '保存失败',
+                    type: 'error',
+                    duration: 3000,
+                })
+            }
+        } catch (error) {
+            ElMessage({
+                message: '保存失败',
+                type: 'error',
+                duration: 3000,
+            })
+        }
+    },
+}
+
+const getMarkWithRow = row => {
+    const baseInfo = {
+        code: row.code,
+        site: row.site,
+        channel: row.channel,
+        href: row.href,
+        modifyuser: row.modifyuser,
+    }
+
+    if (row.cssmark) {
+        return {
+            ...row.cssmark,
+            ...baseInfo,
+        }
+    } else {
+        return baseInfo
+    }
+}
+
+const getLuaParams = row => {
+    const baseInfo = {
+        code: row.code,
+        site: row.site,
+        channel: row.channel,
+        modifyuser: row.modifyuser,
+        claimtime: row.claimtime,
+        recovertime: row.recovertime,
+        priority: row.priority,
+        spiderimportant: row.spiderimportant,
+        modifytime: row.modifytime,
+    }
+    return baseInfo
+}
+
+const editDialogMarkClick = (row) => {
+    // 自定义关闭时间
+    ElMessage({
+        message: `${row.site} ${row.channel} ${row.href}`,
+        showClose: true,
+        duration: 3000,
+    });
+    BrowserOpenURL(row.href)
+}
+
+// table的按钮事件集合
+const tableEvents = {
+    handleDataTag(row) {
+        onlyClickHighlight(row, '_action_clicked_mark')
+        // 自定义关闭时间
+        editDialogMarkClick(row)
+    },
+    handleEdit: (index, row) => {
+        currentEditRow.value = row
+        onlyClickHighlight(row, '_action_clicked_edit')
+        prevClickedRow.value = row
+        ElMessage({
+            message: `${row.site} ${row.channel} ${row.href}`,
+            showClose: true,
+            duration: 3000,
+        });
+        const mark = getMarkWithRow(row)
+        ServerActionCurrentOpenTab(mark)
+        openEditDialog(row)
+    },
+    handleDebug(index, row) {
+        onlyClickHighlight(row, '_action_clicked_debug')
+        runSpiderDialog.value.dialogVisible = true
+        runSpiderDialog.value.setPageData(row)
+        // router.push({
+        //     path: '/run'
+        // });
+    },
+    handleVerify(index, row) {
+        onlyClickHighlight(row, '_action_clicked_verify')
+        loading.value = true
+        if (!row.cssmark) {
+            return ElMessage({
+                message: '没有找到标注信息',
+                type: 'error',
+                duration: 3000,
+            })
+        }
+        const mark = getMarkWithRow(row)
+        VerifySpiderConfig(mark).then(r => {
+            if (r.err === 1 && r.ret) {
+                verifySpiderDialog.value.dialogVisible = true
+                verifySpiderDialog.value.formData = r.ret
+            } else {
+                return ElMessage({
+                    message: r.msg || '验证异常',
+                    type: 'error',
+                    duration: 3000,
+                })
+            }
+        }).finally(() => {
+            loading.value = false
+        })
+    },
+    handleSubmit(index, row) {
+        onlyClickHighlight(row, '_action_clicked_submit')
+        const lua = getLuaParams(row)
+        lua.state = 1
+        ServerActionUpdateCodeState({ lua: lua }).then(r => {
+            if (r.err === 1) {
+                ElMessage({
+                    message: '提交成功',
+                    type: 'success',
+                    duration: 3000,
+                })
+                getTableList()
+            } else {
+                return ElMessage({
+                    message: r.msg || '提交失败',
+                    type: 'error',
+                    duration: 3000,
+                })
+            }
+        })
+    },
+    handleRollback(_, row) {
+        onlyClickHighlight(row, '_action_clicked_rollback')
+        ElMessageBox.alert('确定回退?', '提示', {
+            customClass: 'j-confirm-message-box',
+            confirmButtonText: '确定',
+            callback: (action) => {
+                if (action === 'confirm') {
+                    this.confirmRollback(row)
+                }
+            },
+        })
+    },
+    confirmRollback(row) {
+        const lua = getLuaParams(row)
+        lua.state = 12
+        ServerActionUpdateCodeState({ lua: lua }).then(r => {
+            if (r.err === 1) {
+                ElMessage({
+                    message: '回退成功',
+                    type: 'success',
+                    duration: 3000,
+                })
+                getTableList()
+            } else {
+                return ElMessage({
+                    message: r.msg || '回退失败',
+                    type: 'error',
+                    duration: 3000,
+                })
+            }
+        })
+    },
+}
+
+//Wails事件绑定
+EventsOn("spiderConfigChange", data => {
+    console.log(data)
+    const { key, css, url } = data
+    refreshAndAsyncEditDialog(key, css)
+    // 当触发修改时候,同步给客服端一份
+    if (currentEditRow.value && Object.keys(currentEditRow.value).length <= 0) {
+        const mark = getMarkWithRow(currentEditRow.value)
+        ServerActionCurrentOpenTab(mark)
+    }
+    // 判断标注url和编辑url是否相同
+    // const editUrl = currentEditRow.href
+    // if (url === editUrl) {
+    //     refreshAndAsyncEditDialog(key, css[key])
+    // } else {
+    //     ElMessage({
+    //         message: `标注url和编辑url不匹配,此次更新取消。当前标注url: ${url},当前编辑url: ${editUrl}`,
+    //         type: 'warn',
+    //         duration: 4000,
+    //     })
+    // }
+})
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+    .el-button {
+        &.active {
+            background-color: var(--el-button-hover-bg-color);
+            border-color: var(--el-button-hover-border-color);
+            color: var(--el-button-hover-text-color);
+            outline: none;
+        }
+    }
+}
+
+.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;
+    white-space: nowrap;
+}
+</style>