This commit is contained in:
commit
43e2dff6a3
18
.gitea/workflows/build-push.yml
Normal file
18
.gitea/workflows/build-push.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
name: Build image
|
||||||
|
on: [push]
|
||||||
|
env:
|
||||||
|
HTTPS_PROXY: "http://192.168.31.55:10809"
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: build
|
||||||
|
run: docker build -t ${{ vars.DOCKER_REGISTRY }}/${{ vars.IMAGE_NAME }}:1.3 -f Dockerfile .
|
||||||
|
- name: tag
|
||||||
|
run: docker tag ${{ vars.DOCKER_REGISTRY }}/${{ vars.IMAGE_NAME }}:1.3 ${{ vars.DOCKER_REGISTRY }}/${{ vars.IMAGE_NAME }}:latest
|
||||||
|
- name: push 1.3
|
||||||
|
run: docker push ${{ vars.DOCKER_REGISTRY }}/${{ vars.IMAGE_NAME }}:1.3
|
||||||
|
- name: push latest
|
||||||
|
run: docker push ${{ vars.DOCKER_REGISTRY }}/${{ vars.IMAGE_NAME }}:latest
|
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
FROM oven/bun:1 as front
|
||||||
|
COPY . /build
|
||||||
|
WORKDIR /build/wwwroot
|
||||||
|
RUN bun install && bun run build
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=front /build/wwwroot/dist /usr/share/
|
||||||
|
RUN rm /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# 将自定义配置文件nginx.conf复制到容器内/etc/nginx/conf.d/目录
|
||||||
|
ADD ./nginx.conf /etc/nginx/conf.d/
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Vue 3 + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously Volar) and disable Vetur
|
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[install]
|
||||||
|
registry = "https://registry.npmmirror.com/"
|
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" />-->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>可达鸭海淘</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
21
nginx.conf
Normal file
21
nginx.conf
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ht.timerzz.com;
|
||||||
|
|
||||||
|
#access_log /var/log/nginx/host.access.log main;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/dist;
|
||||||
|
index index.html index.htm;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/v1 {
|
||||||
|
resolver 10.43.0.10 valid=10s; # 6.6.6.6 为自建DNS
|
||||||
|
proxy_pass http://ht-watcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
26
package.json
Normal file
26
package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "wwwroot",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
|
"ant-design-vue": "4.x",
|
||||||
|
"mande": "^2.0.8",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-router": "4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.12.5",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"unocss": "^0.59.0",
|
||||||
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
"vite": "^5.2.0"
|
||||||
|
}
|
||||||
|
}
|
15
src/App.vue
Normal file
15
src/App.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<a-config-provider :locale="zhCN">
|
||||||
|
<layout></layout>
|
||||||
|
</a-config-provider>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import Layout from '@/views/layout/index.vue'
|
||||||
|
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
7
src/api/proxies.js
Normal file
7
src/api/proxies.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import {mande} from "mande";
|
||||||
|
|
||||||
|
const pushers = mande('/api/v1/proxies')
|
||||||
|
|
||||||
|
export const getProxiesStatus = () => {
|
||||||
|
return pushers.get("/status")
|
||||||
|
}
|
11
src/api/pusher.js
Normal file
11
src/api/pusher.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {mande} from "mande";
|
||||||
|
|
||||||
|
const pushers = mande('/api/v1/pushers')
|
||||||
|
|
||||||
|
export const ListPushers = (query) => {
|
||||||
|
return pushers.get({query:query})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddPusher = (opt)=>{
|
||||||
|
return pushers.post(opt)
|
||||||
|
}
|
22
src/api/watcher.js
Normal file
22
src/api/watcher.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {mande} from "mande";
|
||||||
|
|
||||||
|
const watchers = mande('/api/v1/watchers')
|
||||||
|
|
||||||
|
export const ListWatchers = (query) => {
|
||||||
|
return watchers.get({query:query})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateWatcher = (opt)=>{
|
||||||
|
return watchers.post(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeleteWatcher = (uid)=>{
|
||||||
|
return watchers.delete(`/${encodeURIComponent(uid)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StopWatcher = (uid)=>{
|
||||||
|
return watchers.delete(`/${encodeURIComponent(uid)}/status`)
|
||||||
|
}
|
||||||
|
export const StartWatcher = (uid)=>{
|
||||||
|
return watchers.post(`/${encodeURIComponent(uid)}/status`)
|
||||||
|
}
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 271 KiB |
10
src/constants/pusher.js
Normal file
10
src/constants/pusher.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const PUSHER = {
|
||||||
|
ANPUSHER: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WEBSITE_OPTIONS = [
|
||||||
|
{
|
||||||
|
label: 'anPush',
|
||||||
|
value: PUSHER.ANPUSHER
|
||||||
|
}
|
||||||
|
]
|
15
src/constants/website.js
Normal file
15
src/constants/website.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const WEBSITES = {
|
||||||
|
UNKNOWN: 0,
|
||||||
|
COACHOUTLET: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WEBSITE_OPTIONS = [
|
||||||
|
{
|
||||||
|
label: '未知',
|
||||||
|
value: WEBSITES.UNKNOWN
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'coachoutlet',
|
||||||
|
value: WEBSITES.COACHOUTLET
|
||||||
|
}
|
||||||
|
]
|
6
src/css/base.css
Normal file
6
src/css/base.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
html,body,#app {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
9
src/main.js
Normal file
9
src/main.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import './css/base.css'
|
||||||
|
import App from './App.vue'
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
import router from "@/routers/index.js";
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.mount('#app')
|
25
src/routers/index.js
Normal file
25
src/routers/index.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {createRouter, createWebHashHistory} from "vue-router";
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: '/watcher'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/watcher',
|
||||||
|
name: 'watcher',
|
||||||
|
component: ()=>import('@/views/Watcher/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/pusher',
|
||||||
|
name: 'pusher',
|
||||||
|
component: ()=>import('@/views/Pusher/index.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
178
src/views/Pusher/index.vue
Normal file
178
src/views/Pusher/index.vue
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="h-full m-4 bg-white rounded-2 shadow-lg p-8 flex flex-col justify-between space-y-4">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<a-button type="primary" @click="addModal.visible=true" :disabled="loading">添加</a-button>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<a-input placeholder="请输入关键词" v-model:value="query.keyword"></a-input>
|
||||||
|
<a-button type="primary" :disabled="loading" @click="list">搜索</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-full border-0 border-t-1 border-solid border-gray-300 pt-4">
|
||||||
|
<a-spin :spinning="loading" :indicator="indicator">
|
||||||
|
<a-table :dataSource="data.list" :columns="columns" :pagination="false">
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.dataIndex === 'type'">
|
||||||
|
<span>{{WEBSITE_OPTIONS.find(w => w.value === record.type).label}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'updatedAt'">
|
||||||
|
<span>{{moment(record.updatedAt).format('YYYY-MM-DD HH:mm:ss')}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'option'">
|
||||||
|
<span>{{record.option}}</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
<a-pagination :disabled="loading" class="text-right" v-model:current="query.page" :total="data.total" show-less-items />
|
||||||
|
</div>
|
||||||
|
<a-modal v-model:open="addModal.visible" title="添加推送通知" @ok="handleOk" >
|
||||||
|
<a-spin :spinning="addModal.loading" :indicator="indicator">
|
||||||
|
<a-form
|
||||||
|
:model="addModal.data"
|
||||||
|
:label-col="{ span: 4 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
autocomplete="off"
|
||||||
|
@finish="closeAddModal"
|
||||||
|
@finishFailed="closeAddModal"
|
||||||
|
>
|
||||||
|
<a-form-item label="名称" name="name" :rules="[{ required: true, message: '请填写推送名称' }]">
|
||||||
|
<a-input v-model:value="addModal.data.name" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="token" name="option.token" :rules="[{ required: false, message: '请填写token' }]">
|
||||||
|
<a-input v-model:value="addModal.data.option.token" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="channel" name="option.channel" :rules="[{ required: false, message: '请填写channel' }]">
|
||||||
|
<a-input v-model:value="addModal.data.option.channel" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="备注" name="remark">
|
||||||
|
<a-textarea v-model:value="addModal.data.remark" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-spin>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {h, onMounted, reactive, ref} from "vue";
|
||||||
|
import {WEBSITE_OPTIONS} from "@/constants/website.js";
|
||||||
|
import moment from "moment/moment.js";
|
||||||
|
import {LoadingOutlined} from "@ant-design/icons-vue";
|
||||||
|
import {AddPusher, ListPushers} from "@/api/pusher.js";
|
||||||
|
import {PUSHER} from "@/constants/pusher.js";
|
||||||
|
import {message} from "ant-design-vue";
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
list()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const query = reactive({
|
||||||
|
// Website database.WebsiteType `query:"website,omitempty"` //是什么网站
|
||||||
|
// Watch *bool `query:"watch,omitempty"`
|
||||||
|
// Orderable *bool `query:"orderable,omitempty"`
|
||||||
|
// Keyword string `query:"keyword,omitempty"`
|
||||||
|
keyword: '',
|
||||||
|
page: 1,
|
||||||
|
size:10
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const data = ref({
|
||||||
|
total: 0,
|
||||||
|
list:[]
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = ()=>{
|
||||||
|
loading.value = true
|
||||||
|
ListPushers(query).then(res=>{
|
||||||
|
data.value = res
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addModal = reactive({
|
||||||
|
visible: false,
|
||||||
|
data: {
|
||||||
|
type:PUSHER.ANPUSHER,
|
||||||
|
name:'',
|
||||||
|
remark:'',
|
||||||
|
option:{
|
||||||
|
token:'',
|
||||||
|
channel: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const closeAddModal = ()=>{
|
||||||
|
addModal.visible = false
|
||||||
|
addModal.data = {
|
||||||
|
type:PUSHER.ANPUSHER,
|
||||||
|
name:'',
|
||||||
|
remark:'',
|
||||||
|
option:{
|
||||||
|
token:'',
|
||||||
|
channel: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOk = ()=>{
|
||||||
|
AddPusher(addModal.data).then(res=>{
|
||||||
|
message.success("添加成功")
|
||||||
|
}).catch(err => {
|
||||||
|
message.error("添加失败")
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
list(false)
|
||||||
|
closeAddModal()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '配置',
|
||||||
|
dataIndex: 'option',
|
||||||
|
key: 'option',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '更新时间',
|
||||||
|
dataIndex: 'updatedAt',
|
||||||
|
key: 'updatedAt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
dataIndex: 'remark',
|
||||||
|
key: 'remark',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const indicator = h(LoadingOutlined, {
|
||||||
|
style: {
|
||||||
|
fontSize: '32px',
|
||||||
|
},
|
||||||
|
spin: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
258
src/views/Watcher/index.vue
Normal file
258
src/views/Watcher/index.vue
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
<template>
|
||||||
|
<div class="h-full m-4 bg-white rounded-2 shadow-lg p-8 flex flex-col justify-between space-y-4">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<a-button type="primary" @click="addModal.visible=true" :disabled="loading">添加</a-button>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<a-input placeholder="请输入关键词" v-model:value="query.keyword"></a-input>
|
||||||
|
<a-button type="primary" :disabled="loading" @click="list">搜索</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-full border-0 border-t-1 border-solid border-gray-300 pt-4">
|
||||||
|
<a-spin :spinning="loading" :indicator="indicator">
|
||||||
|
<a-table :dataSource="data.list" :columns="columns" :pagination="false">
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'website'">
|
||||||
|
<span>{{WEBSITE_OPTIONS.find(w => w.value === record.website).label}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'watch'">
|
||||||
|
<a-switch :checked="record.watch" @click="watcherStatusChange(!record.watch,record.uid)"/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'name'">
|
||||||
|
<a v-if="record.name !== '' " :href="record.link" target="_blank">{{record.name}}</a>
|
||||||
|
<span v-else>正在抓取信息</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'updatedAt'">
|
||||||
|
<span>{{moment(record.updatedAt).format('YYYY-MM-DD HH:mm:ss')}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'pusherIds'">
|
||||||
|
<template v-if="record.pusherIds" v-for="id in record.pusherIds">
|
||||||
|
<a-tag :bordered="false" color="processing">{{pusher.list.find(p =>p.id === id)?.name}}</a-tag>
|
||||||
|
</template>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'opt'">
|
||||||
|
<a-button type="link" danger @click="onDelete(record)">删除</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
<a-pagination :disabled="loading" class="text-right" v-model:current="query.page" :total="data.total" show-less-items />
|
||||||
|
</div>
|
||||||
|
<a-modal v-model:open="addModal.visible" title="添加监听任务" @ok="handleOk" >
|
||||||
|
<a-spin :spinning="addModal.loading" :indicator="indicator">
|
||||||
|
<a-form
|
||||||
|
:model="addModal.data"
|
||||||
|
:label-col="{ span: 4 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
autocomplete="off"
|
||||||
|
@finish="closeAddModal"
|
||||||
|
@finishFailed="closeAddModal"
|
||||||
|
>
|
||||||
|
<a-form-item label="ID" name="pid" :rules="[{ required: true, message: '请填写商品id' }]">
|
||||||
|
<a-input v-model:value="addModal.data.pid" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="推送" name="pusherIds" :rules="[{ required: true, message: '请选择通知推送' }]">
|
||||||
|
<a-select
|
||||||
|
v-model:value="addModal.data.pusherIds"
|
||||||
|
mode="multiple"
|
||||||
|
@dropdown-visible-change="getPushers"
|
||||||
|
placeholder="请选择通知推送" :fieldNames="{label:'name',value:'id'}"
|
||||||
|
:options="pusher.list"
|
||||||
|
></a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="备注" name="remark">
|
||||||
|
<a-textarea v-model:value="addModal.data.remark" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-spin>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import moment from "moment";
|
||||||
|
import {h, onMounted, reactive, ref} from "vue";
|
||||||
|
import {ListWatchers, CreateWatcher, DeleteWatcher, StartWatcher, StopWatcher} from "@/api/watcher.js";
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||||
|
import {message, Modal} from 'ant-design-vue';
|
||||||
|
import {WEBSITE_OPTIONS} from "@/constants/website.js";
|
||||||
|
import {onBeforeRouteLeave} from "vue-router";
|
||||||
|
import {ListPushers} from "@/api/pusher.js";
|
||||||
|
|
||||||
|
let ticker = null
|
||||||
|
onMounted(()=>{
|
||||||
|
list(false)
|
||||||
|
getPushers()
|
||||||
|
ticker = setInterval(list, 2000, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeRouteLeave(()=>{
|
||||||
|
if(ticker){
|
||||||
|
clearInterval(ticker)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const query = reactive({
|
||||||
|
// Website database.WebsiteType `query:"website,omitempty"` //是什么网站
|
||||||
|
// Watch *bool `query:"watch,omitempty"`
|
||||||
|
// Orderable *bool `query:"orderable,omitempty"`
|
||||||
|
// Keyword string `query:"keyword,omitempty"`
|
||||||
|
page: 1,
|
||||||
|
size:10
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const data = ref({
|
||||||
|
total: 0,
|
||||||
|
list:[]
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = (silent)=>{
|
||||||
|
if(!silent){
|
||||||
|
loading.value = true
|
||||||
|
}
|
||||||
|
ListWatchers(query).then(res=>{
|
||||||
|
data.value = res
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
if(!silent){
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addModal = reactive({
|
||||||
|
visible: false,
|
||||||
|
data: {
|
||||||
|
pid:'',
|
||||||
|
remark:'',
|
||||||
|
pusherIds:[]
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const closeAddModal = ()=>{
|
||||||
|
addModal.visible = false
|
||||||
|
addModal.data = {
|
||||||
|
pid:'',
|
||||||
|
remark:'',
|
||||||
|
pusherIds: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOk = ()=>{
|
||||||
|
CreateWatcher(addModal.data).then(res=>{
|
||||||
|
message.success("添加成功")
|
||||||
|
}).catch(err => {
|
||||||
|
message.error("添加失败")
|
||||||
|
console.log(err)
|
||||||
|
}).finally(()=>{
|
||||||
|
list(false)
|
||||||
|
closeAddModal()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pusher = reactive({
|
||||||
|
list: [],
|
||||||
|
query: {
|
||||||
|
keyword:'',
|
||||||
|
all: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getPushers = ()=>{
|
||||||
|
ListPushers(pusher.query).then(res=>{
|
||||||
|
pusher.list = res.list || []
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDelete = ({name, uid})=>{
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认',
|
||||||
|
content: `确定删除 ${name} 监听?`,
|
||||||
|
centered: true,
|
||||||
|
onOk() {
|
||||||
|
DeleteWatcher(uid).then(res=>{
|
||||||
|
message.success("删除成功")
|
||||||
|
list(false)
|
||||||
|
}).catch(err=>{
|
||||||
|
message.error("删除失败")
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const watcherStatusChange=(changed, uid)=>{
|
||||||
|
const api = changed ? StartWatcher:StopWatcher
|
||||||
|
loading.value = true
|
||||||
|
api(uid).then(res=>{
|
||||||
|
message.success(`${changed?'开启':'关闭'}成功`)
|
||||||
|
}).catch(err=>{
|
||||||
|
message.error(`${changed?'开启':'关闭'}失败`)
|
||||||
|
}).finally(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '货号',
|
||||||
|
dataIndex: 'pid',
|
||||||
|
key: 'pid',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '品牌',
|
||||||
|
dataIndex: 'brand',
|
||||||
|
key: 'brand',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '网站',
|
||||||
|
dataIndex: 'website',
|
||||||
|
key: 'website',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '正在蹲货',
|
||||||
|
dataIndex: 'watch',
|
||||||
|
key: 'watch',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '抓取时间',
|
||||||
|
dataIndex: 'updatedAt',
|
||||||
|
key: 'updatedAt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '推送',
|
||||||
|
key: 'pusherIds',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
dataIndex: 'remark',
|
||||||
|
key: 'remark',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'opt',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const indicator = h(LoadingOutlined, {
|
||||||
|
style: {
|
||||||
|
fontSize: '32px',
|
||||||
|
},
|
||||||
|
spin: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
42
src/views/layout/Aside.vue
Normal file
42
src/views/layout/Aside.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<a-menu
|
||||||
|
@click="onclick"
|
||||||
|
id="aside"
|
||||||
|
style="width: 256px"
|
||||||
|
mode="inline"
|
||||||
|
:items="items"
|
||||||
|
></a-menu>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import {AccountBookOutlined, BellOutlined} from "@ant-design/icons-vue";
|
||||||
|
import {h} from "vue";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
key: 'watcher',
|
||||||
|
icon: () => h(AccountBookOutlined),
|
||||||
|
label: '蹲货',
|
||||||
|
title: '蹲货',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'pusher',
|
||||||
|
icon: () => h(BellOutlined),
|
||||||
|
label: '推送',
|
||||||
|
title: '推送',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const onclick = ({key}) => {
|
||||||
|
router.push({
|
||||||
|
name: key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
38
src/views/layout/Header.vue
Normal file
38
src/views/layout/Header.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div class="h-[100px] shadow-lg flex items-center justify-between px-12 z-10">
|
||||||
|
<div class="flex space-x-4 items-center">
|
||||||
|
<img class="h-[60px]" src="@/assets/logo.png" alt="">
|
||||||
|
<div class="text-[24px] font-bold">
|
||||||
|
可达鸭海淘蹲货
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>当前代理个数: {{proxiesInfo.list.length}}</div>
|
||||||
|
<div class="text-[14px]">代理更新时间:{{moment(proxiesInfo.updated).format('YYYY-MM-DD HH:mm:ss')}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import {onMounted, reactive} from "vue";
|
||||||
|
import {getProxiesStatus} from "@/api/proxies.js";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
const proxiesInfo = reactive({
|
||||||
|
list:[],
|
||||||
|
updated: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
loadProxiesInfo()
|
||||||
|
})
|
||||||
|
const loadProxiesInfo = ()=>{
|
||||||
|
getProxiesStatus().then(res=>{
|
||||||
|
proxiesInfo.list = res.list
|
||||||
|
proxiesInfo.updated = res.updated
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
14
src/views/layout/Main.vue
Normal file
14
src/views/layout/Main.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="h-full w-full bg-[#f0f3f7] flex flex-col">
|
||||||
|
<router-view ></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
17
src/views/layout/index.vue
Normal file
17
src/views/layout/index.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div class="h-full w-full flex flex-col">
|
||||||
|
<Header></Header>
|
||||||
|
<div class="h-full w-full flex">
|
||||||
|
<Aside></Aside>
|
||||||
|
<Main></Main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import Header from "@/views/layout/Header.vue";
|
||||||
|
import Aside from "@/views/layout/Aside.vue";
|
||||||
|
import Main from "@/views/layout/Main.vue";
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
39
vite.config.js
Normal file
39
vite.config.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
import path from 'path'
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
UnoCSS(),
|
||||||
|
Components({
|
||||||
|
resolvers: [
|
||||||
|
AntDesignVueResolver({
|
||||||
|
importStyle: false, // css in js
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
server:{
|
||||||
|
open: true,
|
||||||
|
proxy: {
|
||||||
|
'/api/v1': {
|
||||||
|
target: 'http://172.25.168.160:2280/',
|
||||||
|
// target: 'http://192.168.31.55:2280/',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, "./src")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user