mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-21 17:21:09 +01:00
@@ -137,7 +137,7 @@ class JuejinPipeline(object):
|
||||
|
||||
## 与其他框架比较
|
||||
|
||||
限制以及有一些爬虫管理框架了,因此为啥还要用Crawlab?
|
||||
现在已经有一些爬虫管理框架了,因此为啥还要用Crawlab?
|
||||
|
||||
因为很多现有当平台都依赖于Scrapyd,限制了爬虫的编程语言以及框架,爬虫工程师只能用scrapy和python。当然,scrapy是非常优秀的爬虫框架,但是它不能做一切事情。
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# project variables
|
||||
PROJECT_SOURCE_FILE_FOLDER = '/Users/yeqing/projects/crawlab/spiders'
|
||||
PROJECT_DEPLOY_FILE_FOLDER = '/var/crawlab'
|
||||
PROJECT_LOGS_FOLDER = '/var/logs/crawlab'
|
||||
# 爬虫源码路径
|
||||
PROJECT_SOURCE_FILE_FOLDER = '../spiders'
|
||||
|
||||
# 爬虫部署路径
|
||||
PROJECT_DEPLOY_FILE_FOLDER = '../deployfile'
|
||||
|
||||
PROJECT_LOGS_FOLDER = '../deployfile/logs'
|
||||
PROJECT_TMP_FOLDER = '/tmp'
|
||||
|
||||
# celery variables
|
||||
BROKER_URL = 'redis://192.168.99.100:6379/0'
|
||||
CELERY_RESULT_BACKEND = 'mongodb://192.168.99.100:27017/'
|
||||
BROKER_URL = 'redis://127.0.0.1:6379/0'
|
||||
CELERY_RESULT_BACKEND = 'mongodb://127.0.0.1:27017/'
|
||||
CELERY_MONGODB_BACKEND_SETTINGS = {
|
||||
'database': 'crawlab_test',
|
||||
'taskmeta_collection': 'tasks_celery',
|
||||
@@ -18,11 +22,11 @@ CELERY_ENABLE_UTC = True
|
||||
FLOWER_API_ENDPOINT = 'http://localhost:5555/api'
|
||||
|
||||
# database variables
|
||||
MONGO_HOST = '192.168.99.100'
|
||||
MONGO_HOST = '127.0.0.1'
|
||||
MONGO_PORT = 27017
|
||||
MONGO_DB = 'crawlab_test'
|
||||
|
||||
# flask variables
|
||||
DEBUG = True
|
||||
FLASK_HOST = '127.0.0.1'
|
||||
FLASK_PORT = 8000
|
||||
FLASK_PORT = 8000
|
||||
@@ -25,6 +25,8 @@ class BaseApi(Resource):
|
||||
self.parser.add_argument(arg, type=type)
|
||||
|
||||
def get(self, id=None, action=None):
|
||||
import pdb
|
||||
pdb.set_trace()
|
||||
args = self.parser.parse_args()
|
||||
|
||||
# action by id
|
||||
|
||||
@@ -8,7 +8,7 @@ from db.manager import db_manager
|
||||
from routes.base import BaseApi
|
||||
from utils import jsonify
|
||||
from utils.spider import get_spider_col_fields
|
||||
|
||||
from utils.log import other
|
||||
|
||||
class TaskApi(BaseApi):
|
||||
col_name = 'tasks'
|
||||
@@ -27,6 +27,7 @@ class TaskApi(BaseApi):
|
||||
'code': 400,
|
||||
'error': 'action "%s" invalid' % action
|
||||
}, 400
|
||||
other.info(f"到这了{action},{id}")
|
||||
return getattr(self, action)(id)
|
||||
|
||||
elif id is not None:
|
||||
|
||||
@@ -20,6 +20,7 @@ class Scheduler(object):
|
||||
scheduler = BackgroundScheduler(jobstores=jobstores)
|
||||
|
||||
def execute_spider(self, id: str):
|
||||
|
||||
r = requests.get('http://%s:%s/api/spiders/%s/on_crawl' % (
|
||||
FLASK_HOST,
|
||||
FLASK_PORT,
|
||||
|
||||
75
crawlab/utils/log.py
Normal file
75
crawlab/utils/log.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2019-01-28 15:37
|
||||
# @Author : cxa
|
||||
# @File : log.py
|
||||
# @Software: PyCharm
|
||||
import os
|
||||
import logging
|
||||
import logging.config as log_conf
|
||||
import datetime
|
||||
import coloredlogs
|
||||
|
||||
log_dir = os.path.dirname(os.path.dirname(__file__)) + '/logs'
|
||||
if not os.path.exists(log_dir):
|
||||
os.mkdir(log_dir)
|
||||
today = datetime.datetime.now().strftime("%Y%m%d")
|
||||
|
||||
log_path = os.path.join(log_dir, f'app_{today}.log')
|
||||
|
||||
log_config = {
|
||||
'version': 1.0,
|
||||
'formatters': {
|
||||
'colored_console': {'()': 'coloredlogs.ColoredFormatter',
|
||||
'format': "%(asctime)s - %(name)s - %(levelname)s - %(message)s", 'datefmt': '%H:%M:%S'},
|
||||
'detail': {
|
||||
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
'datefmt': "%Y-%m-%d %H:%M:%S" # 如果不加这个会显示到毫秒。
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(name)s - %(levelname)s - %(message)s',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler', # 日志打印到屏幕显示的类。
|
||||
'level': 'INFO',
|
||||
'formatter': 'colored_console'
|
||||
},
|
||||
'file': {
|
||||
'class': 'logging.handlers.RotatingFileHandler', # 日志打印到文件的类。
|
||||
'maxBytes': 1024 * 1024 * 1024, # 单个文件最大内存
|
||||
'backupCount': 1, # 备份的文件个数
|
||||
'filename': log_path, # 日志文件名
|
||||
'level': 'INFO', # 日志等级
|
||||
'formatter': 'detail', # 调用上面的哪个格式
|
||||
'encoding': 'utf-8', # 编码
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'crawler': {
|
||||
'handlers': ['console', 'file'], # 只打印屏幕
|
||||
'level': 'DEBUG', # 只显示错误的log
|
||||
},
|
||||
'parser': {
|
||||
'handlers': ['file'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
'other': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
'storage': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'INFO',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_conf.dictConfig(log_config)
|
||||
|
||||
crawler = logging.getLogger('crawler')
|
||||
storage = logging.getLogger('storage')
|
||||
other = logging.getLogger('storage')
|
||||
coloredlogs.install(level='DEBUG', logger=crawler)
|
||||
coloredlogs.install(level='DEBUG', logger=storage)
|
||||
coloredlogs.install(level='DEBUG', logger=other)
|
||||
@@ -9,13 +9,17 @@ def get_lang_by_stats(stats: dict) -> LangType:
|
||||
:param stats: stats is generated by utils.file.get_file_suffix_stats
|
||||
:return:
|
||||
"""
|
||||
data = stats.items()
|
||||
data = sorted(data, key=lambda item: item[1])
|
||||
data = list(filter(lambda item: item[0] not in SUFFIX_IGNORE, data))
|
||||
top_suffix = data[-1][0]
|
||||
if FILE_SUFFIX_LANG_MAPPING.get(top_suffix) is not None:
|
||||
return FILE_SUFFIX_LANG_MAPPING.get(top_suffix)
|
||||
return LangType.OTHER
|
||||
try:
|
||||
data = stats.items()
|
||||
data = sorted(data, key=lambda item: item[1])
|
||||
data = list(filter(lambda item: item[0] not in SUFFIX_IGNORE, data))
|
||||
top_suffix = data[-1][0]
|
||||
if FILE_SUFFIX_LANG_MAPPING.get(top_suffix) is not None:
|
||||
return FILE_SUFFIX_LANG_MAPPING.get(top_suffix)
|
||||
return LangType.OTHER
|
||||
except IndexError as e:
|
||||
print(e.args,stats)
|
||||
|
||||
|
||||
|
||||
def get_spider_type(path: str) -> SpiderType:
|
||||
|
||||
82
deployfile/5ca468298ffaed82c65f0f90/juejin_spider 2.js
Normal file
82
deployfile/5ca468298ffaed82c65f0f90/juejin_spider 2.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const puppeteer = require('puppeteer');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
|
||||
(async () => {
|
||||
// browser
|
||||
const browser = await (puppeteer.launch({
|
||||
headless: true
|
||||
}));
|
||||
|
||||
// define start url
|
||||
const url = 'https://juejin.im';
|
||||
|
||||
// start a new page
|
||||
const page = await browser.newPage();
|
||||
|
||||
// navigate to url
|
||||
try {
|
||||
await page.goto(url, {waitUntil: 'domcontentloaded'});
|
||||
await page.waitFor(2000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
// close browser
|
||||
browser.close();
|
||||
|
||||
// exit code 1 indicating an error happened
|
||||
code = 1;
|
||||
process.emit("exit ");
|
||||
process.reallyExit(code);
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// scroll down to fetch more data
|
||||
for (let i = 0; i < 100; i++) {
|
||||
console.log('Pressing PageDown...');
|
||||
await page.keyboard.press('PageDown', 200);
|
||||
await page.waitFor(100);
|
||||
}
|
||||
|
||||
// scrape data
|
||||
const results = await page.evaluate(() => {
|
||||
let results = [];
|
||||
document.querySelectorAll('.entry-list > .item').forEach(el => {
|
||||
if (!el.querySelector('.title')) return;
|
||||
results.push({
|
||||
url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),
|
||||
title: el.querySelector('.title').innerText
|
||||
});
|
||||
});
|
||||
return results;
|
||||
});
|
||||
|
||||
// open database connection
|
||||
const client = await MongoClient.connect('mongodb://192.168.99.100:27017');
|
||||
let db = await client.db('crawlab_test');
|
||||
const colName = process.env.CRAWLAB_COLLECTION || 'results_juejin';
|
||||
const taskId = process.env.CRAWLAB_TASK_ID;
|
||||
const col = db.collection(colName);
|
||||
|
||||
// save to database
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
// de-duplication
|
||||
const r = await col.findOne({url: results[i]});
|
||||
if (r) continue;
|
||||
|
||||
// assign taskID
|
||||
results[i].task_id = taskId;
|
||||
results[i].source = 'juejin';
|
||||
|
||||
// insert row
|
||||
await col.insertOne(results[i]);
|
||||
}
|
||||
|
||||
console.log(`results.length: ${results.length}`);
|
||||
|
||||
// close database connection
|
||||
client.close();
|
||||
|
||||
// shutdown browser
|
||||
browser.close();
|
||||
})();
|
||||
82
deployfile/5ca468298ffaed82c65f0f90/juejin_spider.js
Normal file
82
deployfile/5ca468298ffaed82c65f0f90/juejin_spider.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const puppeteer = require('puppeteer');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
|
||||
(async () => {
|
||||
// browser
|
||||
const browser = await (puppeteer.launch({
|
||||
headless: true
|
||||
}));
|
||||
|
||||
// define start url
|
||||
const url = 'https://juejin.im';
|
||||
|
||||
// start a new page
|
||||
const page = await browser.newPage();
|
||||
|
||||
// navigate to url
|
||||
try {
|
||||
await page.goto(url, {waitUntil: 'domcontentloaded'});
|
||||
await page.waitFor(2000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
// close browser
|
||||
browser.close();
|
||||
|
||||
// exit code 1 indicating an error happened
|
||||
code = 1;
|
||||
process.emit("exit ");
|
||||
process.reallyExit(code);
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// scroll down to fetch more data
|
||||
for (let i = 0; i < 100; i++) {
|
||||
console.log('Pressing PageDown...');
|
||||
await page.keyboard.press('PageDown', 200);
|
||||
await page.waitFor(100);
|
||||
}
|
||||
|
||||
// scrape data
|
||||
const results = await page.evaluate(() => {
|
||||
let results = [];
|
||||
document.querySelectorAll('.entry-list > .item').forEach(el => {
|
||||
if (!el.querySelector('.title')) return;
|
||||
results.push({
|
||||
url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),
|
||||
title: el.querySelector('.title').innerText
|
||||
});
|
||||
});
|
||||
return results;
|
||||
});
|
||||
|
||||
// open database connection
|
||||
const client = await MongoClient.connect('mongodb://127.0.0.1:27017');
|
||||
let db = await client.db('crawlab_test');
|
||||
const colName = process.env.CRAWLAB_COLLECTION || 'results_juejin';
|
||||
const taskId = process.env.CRAWLAB_TASK_ID;
|
||||
const col = db.collection(colName);
|
||||
|
||||
// save to database
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
// de-duplication
|
||||
const r = await col.findOne({url: results[i]});
|
||||
if (r) continue;
|
||||
|
||||
// assign taskID
|
||||
results[i].task_id = taskId;
|
||||
results[i].source = 'juejin';
|
||||
|
||||
// insert row
|
||||
await col.insertOne(results[i]);
|
||||
}
|
||||
|
||||
console.log(`results.length: ${results.length}`);
|
||||
|
||||
// close database connection
|
||||
client.close();
|
||||
|
||||
// shutdown browser
|
||||
browser.close();
|
||||
})();
|
||||
82
deployfile/5ca46b508ffaed82c65f0fa0/juejin_spider 2.js
Normal file
82
deployfile/5ca46b508ffaed82c65f0fa0/juejin_spider 2.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const puppeteer = require('puppeteer');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
|
||||
(async () => {
|
||||
// browser
|
||||
const browser = await (puppeteer.launch({
|
||||
headless: true
|
||||
}));
|
||||
|
||||
// define start url
|
||||
const url = 'https://juejin.im';
|
||||
|
||||
// start a new page
|
||||
const page = await browser.newPage();
|
||||
|
||||
// navigate to url
|
||||
try {
|
||||
await page.goto(url, {waitUntil: 'domcontentloaded'});
|
||||
await page.waitFor(2000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
// close browser
|
||||
browser.close();
|
||||
|
||||
// exit code 1 indicating an error happened
|
||||
code = 1;
|
||||
process.emit("exit ");
|
||||
process.reallyExit(code);
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// scroll down to fetch more data
|
||||
for (let i = 0; i < 100; i++) {
|
||||
console.log('Pressing PageDown...');
|
||||
await page.keyboard.press('PageDown', 200);
|
||||
await page.waitFor(100);
|
||||
}
|
||||
|
||||
// scrape data
|
||||
const results = await page.evaluate(() => {
|
||||
let results = [];
|
||||
document.querySelectorAll('.entry-list > .item').forEach(el => {
|
||||
if (!el.querySelector('.title')) return;
|
||||
results.push({
|
||||
url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),
|
||||
title: el.querySelector('.title').innerText
|
||||
});
|
||||
});
|
||||
return results;
|
||||
});
|
||||
|
||||
// open database connection
|
||||
const client = await MongoClient.connect('mongodb://192.168.99.100:27017');
|
||||
let db = await client.db('crawlab_test');
|
||||
const colName = process.env.CRAWLAB_COLLECTION || 'results_juejin';
|
||||
const taskId = process.env.CRAWLAB_TASK_ID;
|
||||
const col = db.collection(colName);
|
||||
|
||||
// save to database
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
// de-duplication
|
||||
const r = await col.findOne({url: results[i]});
|
||||
if (r) continue;
|
||||
|
||||
// assign taskID
|
||||
results[i].task_id = taskId;
|
||||
results[i].source = 'juejin';
|
||||
|
||||
// insert row
|
||||
await col.insertOne(results[i]);
|
||||
}
|
||||
|
||||
console.log(`results.length: ${results.length}`);
|
||||
|
||||
// close database connection
|
||||
client.close();
|
||||
|
||||
// shutdown browser
|
||||
browser.close();
|
||||
})();
|
||||
82
deployfile/5ca46b508ffaed82c65f0fa0/juejin_spider.js
Normal file
82
deployfile/5ca46b508ffaed82c65f0fa0/juejin_spider.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const puppeteer = require('puppeteer');
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
|
||||
(async () => {
|
||||
// browser
|
||||
const browser = await (puppeteer.launch({
|
||||
headless: true
|
||||
}));
|
||||
|
||||
// define start url
|
||||
const url = 'https://juejin.im';
|
||||
|
||||
// start a new page
|
||||
const page = await browser.newPage();
|
||||
|
||||
// navigate to url
|
||||
try {
|
||||
await page.goto(url, {waitUntil: 'domcontentloaded'});
|
||||
await page.waitFor(2000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
// close browser
|
||||
browser.close();
|
||||
|
||||
// exit code 1 indicating an error happened
|
||||
code = 1;
|
||||
process.emit("exit ");
|
||||
process.reallyExit(code);
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// scroll down to fetch more data
|
||||
for (let i = 0; i < 100; i++) {
|
||||
console.log('Pressing PageDown...');
|
||||
await page.keyboard.press('PageDown', 200);
|
||||
await page.waitFor(100);
|
||||
}
|
||||
|
||||
// scrape data
|
||||
const results = await page.evaluate(() => {
|
||||
let results = [];
|
||||
document.querySelectorAll('.entry-list > .item').forEach(el => {
|
||||
if (!el.querySelector('.title')) return;
|
||||
results.push({
|
||||
url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),
|
||||
title: el.querySelector('.title').innerText
|
||||
});
|
||||
});
|
||||
return results;
|
||||
});
|
||||
|
||||
// open database connection
|
||||
const client = await MongoClient.connect('mongodb://127.0.0.1:27017');
|
||||
let db = await client.db('crawlab_test');
|
||||
const colName = process.env.CRAWLAB_COLLECTION || 'results_juejin';
|
||||
const taskId = process.env.CRAWLAB_TASK_ID;
|
||||
const col = db.collection(colName);
|
||||
|
||||
// save to database
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
// de-duplication
|
||||
const r = await col.findOne({url: results[i]});
|
||||
if (r) continue;
|
||||
|
||||
// assign taskID
|
||||
results[i].task_id = taskId;
|
||||
results[i].source = 'juejin';
|
||||
|
||||
// insert row
|
||||
await col.insertOne(results[i]);
|
||||
}
|
||||
|
||||
console.log(`results.length: ${results.length}`);
|
||||
|
||||
// close database connection
|
||||
client.close();
|
||||
|
||||
// shutdown browser
|
||||
browser.close();
|
||||
})();
|
||||
2
docs/Architecture/App 2.md
Normal file
2
docs/Architecture/App 2.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# App
|
||||
|
||||
6
docs/Concept/Deploy 2.md
Normal file
6
docs/Concept/Deploy 2.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 部署
|
||||
|
||||
所有爬虫在运行前需要被部署当相应当节点中。
|
||||
|
||||
部署时,爬虫会被打包到相应的目录中,方便环境隔离,开发环境的爬虫和生产环境的爬虫需要打包部署来实现隔离。
|
||||
|
||||
2
docs/Examples/README 2.md
Normal file
2
docs/Examples/README 2.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Examples
|
||||
|
||||
22
docs/QuickStart/Installation 2.md
Normal file
22
docs/QuickStart/Installation 2.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 安装
|
||||
|
||||
最快安装Crawlab的方式是克隆一份代码到本地
|
||||
|
||||
```bash
|
||||
git clone https://github.com/tikazyq/crawlab
|
||||
```
|
||||
|
||||
安装类库
|
||||
|
||||
```bash
|
||||
# 安装后台类库
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
```bash
|
||||
# 安装前台类库
|
||||
cd frontend
|
||||
npm install
|
||||
```
|
||||
|
||||
|
||||
436
docs/_book/Architecture/App 2.html
Normal file
436
docs/_book/Architecture/App 2.html
Normal file
@@ -0,0 +1,436 @@
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="" >
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<title>App · GitBook</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="description" content="">
|
||||
<meta name="generator" content="GitBook 3.2.3">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="HandheldFriendly" content="true"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="../gitbook/images/apple-touch-icon-precomposed-152.png">
|
||||
<link rel="shortcut icon" href="../gitbook/images/favicon.ico" type="image/x-icon">
|
||||
|
||||
|
||||
<link rel="next" href="../Examples/" />
|
||||
|
||||
|
||||
<link rel="prev" href="Celery.html" />
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="book">
|
||||
<div class="book-summary">
|
||||
|
||||
|
||||
<div id="book-search-input" role="search">
|
||||
<input type="text" placeholder="Type to search" />
|
||||
</div>
|
||||
|
||||
|
||||
<nav role="navigation">
|
||||
|
||||
|
||||
|
||||
<ul class="summary">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.1" data-path="../">
|
||||
|
||||
<a href="../">
|
||||
|
||||
|
||||
简介
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.2" data-path="../QuickStart/">
|
||||
|
||||
<a href="../QuickStart/">
|
||||
|
||||
|
||||
快速开始
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.2.1" data-path="../QuickStart/Installation.html">
|
||||
|
||||
<a href="../QuickStart/Installation.html">
|
||||
|
||||
|
||||
安装
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.2.2" data-path="../QuickStart/Run.html">
|
||||
|
||||
<a href="../QuickStart/Run.html">
|
||||
|
||||
|
||||
运行
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3" data-path="../Concept/">
|
||||
|
||||
<a href="../Concept/">
|
||||
|
||||
|
||||
概念
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.3.1" data-path="../Concept/Node.html">
|
||||
|
||||
<a href="../Concept/Node.html">
|
||||
|
||||
|
||||
节点
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.2" data-path="../Concept/Spider.html">
|
||||
|
||||
<a href="../Concept/Spider.html">
|
||||
|
||||
|
||||
爬虫
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.3" data-path="../Concept/Task.html">
|
||||
|
||||
<a href="../Concept/Task.html">
|
||||
|
||||
|
||||
任务
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.4" data-path="../Concept/Deploy.html">
|
||||
|
||||
<a href="../Concept/Deploy.html">
|
||||
|
||||
|
||||
部署
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4" data-path="./">
|
||||
|
||||
<a href="./">
|
||||
|
||||
|
||||
架构
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.4.1" data-path="Celery.html">
|
||||
|
||||
<a href="Celery.html">
|
||||
|
||||
|
||||
Celery
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter active" data-level="1.4.2" data-path="App.html">
|
||||
|
||||
<a href="App.html">
|
||||
|
||||
|
||||
App
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
Examples
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.5.1" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
与Scrapy集成
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.2" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
与Puppeteer集成
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="divider"></li>
|
||||
|
||||
<li>
|
||||
<a href="https://www.gitbook.com" target="blank" class="gitbook-link">
|
||||
Published with GitBook
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="book-body">
|
||||
|
||||
<div class="body-inner">
|
||||
|
||||
|
||||
|
||||
<div class="book-header" role="navigation">
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<h1>
|
||||
<i class="fa fa-circle-o-notch fa-spin"></i>
|
||||
<a href=".." >App</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="page-wrapper" tabindex="-1" role="main">
|
||||
<div class="page-inner">
|
||||
|
||||
<div id="book-search-results">
|
||||
<div class="search-noresults">
|
||||
|
||||
<section class="normal markdown-section">
|
||||
|
||||
<h1 id="app">App</h1>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<div class="search-results">
|
||||
<div class="has-results">
|
||||
|
||||
<h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
|
||||
<ul class="search-results-list"></ul>
|
||||
|
||||
</div>
|
||||
<div class="no-results">
|
||||
|
||||
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<a href="Celery.html" class="navigation navigation-prev " aria-label="Previous page: Celery">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<a href="../Examples/" class="navigation navigation-next " aria-label="Next page: Examples">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var gitbook = gitbook || [];
|
||||
gitbook.push(function() {
|
||||
gitbook.page.hasChanged({"page":{"title":"App","level":"1.4.2","depth":2,"next":{"title":"Examples","level":"1.5","depth":1,"path":"Examples/README.md","ref":"Examples/README.md","articles":[{"title":"与Scrapy集成","level":"1.5.1","depth":2,"path":"Examples/README.md","ref":"Examples/README.md","articles":[]},{"title":"与Puppeteer集成","level":"1.5.2","depth":2,"path":"Examples/README.md","ref":"Examples/README.md","articles":[]}]},"previous":{"title":"Celery","level":"1.4.1","depth":2,"path":"Architecture/Celery.md","ref":"Architecture/Celery.md","articles":[]},"dir":"ltr"},"config":{"gitbook":"*","theme":"default","variables":{},"plugins":[],"pluginsConfig":{"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"}},"file":{"path":"Architecture/App.md","mtime":"2019-03-28T11:49:43.000Z","type":"markdown"},"gitbook":{"version":"3.2.3","time":"2019-03-28T12:07:05.349Z"},"basePath":"..","book":{"language":""}});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook.js"></script>
|
||||
<script src="../gitbook/theme.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
438
docs/_book/Concept/Deploy 2.html
Normal file
438
docs/_book/Concept/Deploy 2.html
Normal file
@@ -0,0 +1,438 @@
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="" >
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<title>部署 · GitBook</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="description" content="">
|
||||
<meta name="generator" content="GitBook 3.2.3">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="HandheldFriendly" content="true"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="../gitbook/images/apple-touch-icon-precomposed-152.png">
|
||||
<link rel="shortcut icon" href="../gitbook/images/favicon.ico" type="image/x-icon">
|
||||
|
||||
|
||||
<link rel="next" href="../Architecture/" />
|
||||
|
||||
|
||||
<link rel="prev" href="Task.html" />
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="book">
|
||||
<div class="book-summary">
|
||||
|
||||
|
||||
<div id="book-search-input" role="search">
|
||||
<input type="text" placeholder="Type to search" />
|
||||
</div>
|
||||
|
||||
|
||||
<nav role="navigation">
|
||||
|
||||
|
||||
|
||||
<ul class="summary">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.1" data-path="../">
|
||||
|
||||
<a href="../">
|
||||
|
||||
|
||||
简介
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.2" data-path="../QuickStart/">
|
||||
|
||||
<a href="../QuickStart/">
|
||||
|
||||
|
||||
快速开始
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.2.1" data-path="../QuickStart/Installation.html">
|
||||
|
||||
<a href="../QuickStart/Installation.html">
|
||||
|
||||
|
||||
安装
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.2.2" data-path="../QuickStart/Run.html">
|
||||
|
||||
<a href="../QuickStart/Run.html">
|
||||
|
||||
|
||||
运行
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3" data-path="./">
|
||||
|
||||
<a href="./">
|
||||
|
||||
|
||||
概念
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.3.1" data-path="Node.html">
|
||||
|
||||
<a href="Node.html">
|
||||
|
||||
|
||||
节点
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.2" data-path="Spider.html">
|
||||
|
||||
<a href="Spider.html">
|
||||
|
||||
|
||||
爬虫
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.3" data-path="Task.html">
|
||||
|
||||
<a href="Task.html">
|
||||
|
||||
|
||||
任务
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter active" data-level="1.3.4" data-path="Deploy.html">
|
||||
|
||||
<a href="Deploy.html">
|
||||
|
||||
|
||||
部署
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4" data-path="../Architecture/">
|
||||
|
||||
<a href="../Architecture/">
|
||||
|
||||
|
||||
架构
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.4.1" data-path="../Architecture/Celery.html">
|
||||
|
||||
<a href="../Architecture/Celery.html">
|
||||
|
||||
|
||||
Celery
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4.2" data-path="../Architecture/App.html">
|
||||
|
||||
<a href="../Architecture/App.html">
|
||||
|
||||
|
||||
App
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
Examples
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.5.1" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
与Scrapy集成
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.2" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
与Puppeteer集成
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="divider"></li>
|
||||
|
||||
<li>
|
||||
<a href="https://www.gitbook.com" target="blank" class="gitbook-link">
|
||||
Published with GitBook
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="book-body">
|
||||
|
||||
<div class="body-inner">
|
||||
|
||||
|
||||
|
||||
<div class="book-header" role="navigation">
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<h1>
|
||||
<i class="fa fa-circle-o-notch fa-spin"></i>
|
||||
<a href=".." >部署</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="page-wrapper" tabindex="-1" role="main">
|
||||
<div class="page-inner">
|
||||
|
||||
<div id="book-search-results">
|
||||
<div class="search-noresults">
|
||||
|
||||
<section class="normal markdown-section">
|
||||
|
||||
<h1 id="部署">部署</h1>
|
||||
<p>所有爬虫在运行前需要被部署当相应当节点中。</p>
|
||||
<p>部署时,爬虫会被打包到相应的目录中,方便环境隔离,开发环境的爬虫和生产环境的爬虫需要打包部署来实现隔离。</p>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<div class="search-results">
|
||||
<div class="has-results">
|
||||
|
||||
<h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
|
||||
<ul class="search-results-list"></ul>
|
||||
|
||||
</div>
|
||||
<div class="no-results">
|
||||
|
||||
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<a href="Task.html" class="navigation navigation-prev " aria-label="Previous page: 任务">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<a href="../Architecture/" class="navigation navigation-next " aria-label="Next page: 架构">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var gitbook = gitbook || [];
|
||||
gitbook.push(function() {
|
||||
gitbook.page.hasChanged({"page":{"title":"部署","level":"1.3.4","depth":2,"next":{"title":"架构","level":"1.4","depth":1,"path":"Architecture/README.md","ref":"Architecture/README.md","articles":[{"title":"Celery","level":"1.4.1","depth":2,"path":"Architecture/Celery.md","ref":"Architecture/Celery.md","articles":[]},{"title":"App","level":"1.4.2","depth":2,"path":"Architecture/App.md","ref":"Architecture/App.md","articles":[]}]},"previous":{"title":"任务","level":"1.3.3","depth":2,"path":"Concept/Task.md","ref":"Concept/Task.md","articles":[]},"dir":"ltr"},"config":{"gitbook":"*","theme":"default","variables":{},"plugins":[],"pluginsConfig":{"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"}},"file":{"path":"Concept/Deploy.md","mtime":"2019-03-28T12:06:24.000Z","type":"markdown"},"gitbook":{"version":"3.2.3","time":"2019-03-28T12:07:05.349Z"},"basePath":"..","book":{"language":""}});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook.js"></script>
|
||||
<script src="../gitbook/theme.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
436
docs/_book/Examples/index 2.html
Normal file
436
docs/_book/Examples/index 2.html
Normal file
@@ -0,0 +1,436 @@
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="" >
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<title>Examples · GitBook</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="description" content="">
|
||||
<meta name="generator" content="GitBook 3.2.3">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="HandheldFriendly" content="true"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="../gitbook/images/apple-touch-icon-precomposed-152.png">
|
||||
<link rel="shortcut icon" href="../gitbook/images/favicon.ico" type="image/x-icon">
|
||||
|
||||
|
||||
<link rel="next" href="./" />
|
||||
|
||||
|
||||
<link rel="prev" href="../Architecture/App.html" />
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="book">
|
||||
<div class="book-summary">
|
||||
|
||||
|
||||
<div id="book-search-input" role="search">
|
||||
<input type="text" placeholder="Type to search" />
|
||||
</div>
|
||||
|
||||
|
||||
<nav role="navigation">
|
||||
|
||||
|
||||
|
||||
<ul class="summary">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.1" data-path="../">
|
||||
|
||||
<a href="../">
|
||||
|
||||
|
||||
简介
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.2" data-path="../QuickStart/">
|
||||
|
||||
<a href="../QuickStart/">
|
||||
|
||||
|
||||
快速开始
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.2.1" data-path="../QuickStart/Installation.html">
|
||||
|
||||
<a href="../QuickStart/Installation.html">
|
||||
|
||||
|
||||
安装
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.2.2" data-path="../QuickStart/Run.html">
|
||||
|
||||
<a href="../QuickStart/Run.html">
|
||||
|
||||
|
||||
运行
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3" data-path="../Concept/">
|
||||
|
||||
<a href="../Concept/">
|
||||
|
||||
|
||||
概念
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.3.1" data-path="../Concept/Node.html">
|
||||
|
||||
<a href="../Concept/Node.html">
|
||||
|
||||
|
||||
节点
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.2" data-path="../Concept/Spider.html">
|
||||
|
||||
<a href="../Concept/Spider.html">
|
||||
|
||||
|
||||
爬虫
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.3" data-path="../Concept/Task.html">
|
||||
|
||||
<a href="../Concept/Task.html">
|
||||
|
||||
|
||||
任务
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.4" data-path="../Concept/Deploy.html">
|
||||
|
||||
<a href="../Concept/Deploy.html">
|
||||
|
||||
|
||||
部署
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4" data-path="../Architecture/">
|
||||
|
||||
<a href="../Architecture/">
|
||||
|
||||
|
||||
架构
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.4.1" data-path="../Architecture/Celery.html">
|
||||
|
||||
<a href="../Architecture/Celery.html">
|
||||
|
||||
|
||||
Celery
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4.2" data-path="../Architecture/App.html">
|
||||
|
||||
<a href="../Architecture/App.html">
|
||||
|
||||
|
||||
App
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter active" data-level="1.5" data-path="./">
|
||||
|
||||
<a href="./">
|
||||
|
||||
|
||||
Examples
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter active" data-level="1.5.1" data-path="./">
|
||||
|
||||
<a href="./">
|
||||
|
||||
|
||||
与Scrapy集成
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter active" data-level="1.5.2" data-path="./">
|
||||
|
||||
<a href="./">
|
||||
|
||||
|
||||
与Puppeteer集成
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="divider"></li>
|
||||
|
||||
<li>
|
||||
<a href="https://www.gitbook.com" target="blank" class="gitbook-link">
|
||||
Published with GitBook
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="book-body">
|
||||
|
||||
<div class="body-inner">
|
||||
|
||||
|
||||
|
||||
<div class="book-header" role="navigation">
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<h1>
|
||||
<i class="fa fa-circle-o-notch fa-spin"></i>
|
||||
<a href=".." >Examples</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="page-wrapper" tabindex="-1" role="main">
|
||||
<div class="page-inner">
|
||||
|
||||
<div id="book-search-results">
|
||||
<div class="search-noresults">
|
||||
|
||||
<section class="normal markdown-section">
|
||||
|
||||
<h1 id="examples">Examples</h1>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<div class="search-results">
|
||||
<div class="has-results">
|
||||
|
||||
<h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
|
||||
<ul class="search-results-list"></ul>
|
||||
|
||||
</div>
|
||||
<div class="no-results">
|
||||
|
||||
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<a href="../Architecture/App.html" class="navigation navigation-prev " aria-label="Previous page: App">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<a href="./" class="navigation navigation-next " aria-label="Next page: 与Scrapy集成">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var gitbook = gitbook || [];
|
||||
gitbook.push(function() {
|
||||
gitbook.page.hasChanged({"page":{"title":"Examples","level":"1.5","depth":1,"next":{"title":"与Scrapy集成","level":"1.5.1","depth":2,"path":"Examples/README.md","ref":"Examples/README.md","articles":[]},"previous":{"title":"App","level":"1.4.2","depth":2,"path":"Architecture/App.md","ref":"Architecture/App.md","articles":[]},"dir":"ltr"},"config":{"gitbook":"*","theme":"default","variables":{},"plugins":[],"pluginsConfig":{"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"}},"file":{"path":"Examples/README.md","mtime":"2019-03-28T11:41:28.000Z","type":"markdown"},"gitbook":{"version":"3.2.3","time":"2019-03-28T12:07:05.349Z"},"basePath":"..","book":{"language":""}});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook.js"></script>
|
||||
<script src="../gitbook/theme.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
447
docs/_book/QuickStart/Installation 2.html
Normal file
447
docs/_book/QuickStart/Installation 2.html
Normal file
@@ -0,0 +1,447 @@
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="" >
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<title>安装 · GitBook</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="description" content="">
|
||||
<meta name="generator" content="GitBook 3.2.3">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="HandheldFriendly" content="true"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="../gitbook/images/apple-touch-icon-precomposed-152.png">
|
||||
<link rel="shortcut icon" href="../gitbook/images/favicon.ico" type="image/x-icon">
|
||||
|
||||
|
||||
<link rel="next" href="Run.html" />
|
||||
|
||||
|
||||
<link rel="prev" href="./" />
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="book">
|
||||
<div class="book-summary">
|
||||
|
||||
|
||||
<div id="book-search-input" role="search">
|
||||
<input type="text" placeholder="Type to search" />
|
||||
</div>
|
||||
|
||||
|
||||
<nav role="navigation">
|
||||
|
||||
|
||||
|
||||
<ul class="summary">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.1" data-path="../">
|
||||
|
||||
<a href="../">
|
||||
|
||||
|
||||
简介
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.2" data-path="./">
|
||||
|
||||
<a href="./">
|
||||
|
||||
|
||||
快速开始
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter active" data-level="1.2.1" data-path="Installation.html">
|
||||
|
||||
<a href="Installation.html">
|
||||
|
||||
|
||||
安装
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.2.2" data-path="Run.html">
|
||||
|
||||
<a href="Run.html">
|
||||
|
||||
|
||||
运行
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3" data-path="../Concept/">
|
||||
|
||||
<a href="../Concept/">
|
||||
|
||||
|
||||
概念
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.3.1" data-path="../Concept/Node.html">
|
||||
|
||||
<a href="../Concept/Node.html">
|
||||
|
||||
|
||||
节点
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.2" data-path="../Concept/Spider.html">
|
||||
|
||||
<a href="../Concept/Spider.html">
|
||||
|
||||
|
||||
爬虫
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.3" data-path="../Concept/Task.html">
|
||||
|
||||
<a href="../Concept/Task.html">
|
||||
|
||||
|
||||
任务
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.3.4" data-path="../Concept/Deploy.html">
|
||||
|
||||
<a href="../Concept/Deploy.html">
|
||||
|
||||
|
||||
部署
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4" data-path="../Architecture/">
|
||||
|
||||
<a href="../Architecture/">
|
||||
|
||||
|
||||
架构
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.4.1" data-path="../Architecture/Celery.html">
|
||||
|
||||
<a href="../Architecture/Celery.html">
|
||||
|
||||
|
||||
Celery
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.4.2" data-path="../Architecture/App.html">
|
||||
|
||||
<a href="../Architecture/App.html">
|
||||
|
||||
|
||||
App
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
Examples
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="1.5.1" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
与Scrapy集成
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="1.5.2" data-path="../Examples/">
|
||||
|
||||
<a href="../Examples/">
|
||||
|
||||
|
||||
与Puppeteer集成
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="divider"></li>
|
||||
|
||||
<li>
|
||||
<a href="https://www.gitbook.com" target="blank" class="gitbook-link">
|
||||
Published with GitBook
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="book-body">
|
||||
|
||||
<div class="body-inner">
|
||||
|
||||
|
||||
|
||||
<div class="book-header" role="navigation">
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<h1>
|
||||
<i class="fa fa-circle-o-notch fa-spin"></i>
|
||||
<a href=".." >安装</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="page-wrapper" tabindex="-1" role="main">
|
||||
<div class="page-inner">
|
||||
|
||||
<div id="book-search-results">
|
||||
<div class="search-noresults">
|
||||
|
||||
<section class="normal markdown-section">
|
||||
|
||||
<h1 id="安装">安装</h1>
|
||||
<p>最快安装Crawlab的方式是克隆一份代码到本地</p>
|
||||
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/tikazyq/crawlab
|
||||
</code></pre>
|
||||
<p>安装类库</p>
|
||||
<pre><code class="lang-bash"><span class="hljs-comment"># 安装后台类库</span>
|
||||
pip install -r requirements.txt
|
||||
</code></pre>
|
||||
<pre><code class="lang-bash"><span class="hljs-comment"># 安装前台类库</span>
|
||||
<span class="hljs-built_in">cd</span> frontend
|
||||
npm install
|
||||
</code></pre>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<div class="search-results">
|
||||
<div class="has-results">
|
||||
|
||||
<h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
|
||||
<ul class="search-results-list"></ul>
|
||||
|
||||
</div>
|
||||
<div class="no-results">
|
||||
|
||||
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<a href="./" class="navigation navigation-prev " aria-label="Previous page: 快速开始">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<a href="Run.html" class="navigation navigation-next " aria-label="Next page: 运行">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var gitbook = gitbook || [];
|
||||
gitbook.push(function() {
|
||||
gitbook.page.hasChanged({"page":{"title":"安装","level":"1.2.1","depth":2,"next":{"title":"运行","level":"1.2.2","depth":2,"path":"QuickStart/Run.md","ref":"QuickStart/Run.md","articles":[]},"previous":{"title":"快速开始","level":"1.2","depth":1,"path":"QuickStart/README.md","ref":"QuickStart/README.md","articles":[{"title":"安装","level":"1.2.1","depth":2,"path":"QuickStart/Installation.md","ref":"QuickStart/Installation.md","articles":[]},{"title":"运行","level":"1.2.2","depth":2,"path":"QuickStart/Run.md","ref":"QuickStart/Run.md","articles":[]}]},"dir":"ltr"},"config":{"gitbook":"*","theme":"default","variables":{},"plugins":[],"pluginsConfig":{"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"}},"file":{"path":"QuickStart/Installation.md","mtime":"2019-03-28T11:55:48.000Z","type":"markdown"},"gitbook":{"version":"3.2.3","time":"2019-03-28T12:07:05.349Z"},"basePath":"..","book":{"language":""}});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook.js"></script>
|
||||
<script src="../gitbook/theme.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
BIN
docs/_book/gitbook/fonts/fontawesome/FontAwesome 2.otf
Normal file
BIN
docs/_book/gitbook/fonts/fontawesome/FontAwesome 2.otf
Normal file
Binary file not shown.
240
docs/_book/gitbook/gitbook-plugin-fontsettings/fontsettings 2.js
Normal file
240
docs/_book/gitbook/gitbook-plugin-fontsettings/fontsettings 2.js
Normal file
@@ -0,0 +1,240 @@
|
||||
require(['gitbook', 'jquery'], function(gitbook, $) {
|
||||
// Configuration
|
||||
var MAX_SIZE = 4,
|
||||
MIN_SIZE = 0,
|
||||
BUTTON_ID;
|
||||
|
||||
// Current fontsettings state
|
||||
var fontState;
|
||||
|
||||
// Default themes
|
||||
var THEMES = [
|
||||
{
|
||||
config: 'white',
|
||||
text: 'White',
|
||||
id: 0
|
||||
},
|
||||
{
|
||||
config: 'sepia',
|
||||
text: 'Sepia',
|
||||
id: 1
|
||||
},
|
||||
{
|
||||
config: 'night',
|
||||
text: 'Night',
|
||||
id: 2
|
||||
}
|
||||
];
|
||||
|
||||
// Default font families
|
||||
var FAMILIES = [
|
||||
{
|
||||
config: 'serif',
|
||||
text: 'Serif',
|
||||
id: 0
|
||||
},
|
||||
{
|
||||
config: 'sans',
|
||||
text: 'Sans',
|
||||
id: 1
|
||||
}
|
||||
];
|
||||
|
||||
// Return configured themes
|
||||
function getThemes() {
|
||||
return THEMES;
|
||||
}
|
||||
|
||||
// Modify configured themes
|
||||
function setThemes(themes) {
|
||||
THEMES = themes;
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
// Return configured font families
|
||||
function getFamilies() {
|
||||
return FAMILIES;
|
||||
}
|
||||
|
||||
// Modify configured font families
|
||||
function setFamilies(families) {
|
||||
FAMILIES = families;
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
// Save current font settings
|
||||
function saveFontSettings() {
|
||||
gitbook.storage.set('fontState', fontState);
|
||||
update();
|
||||
}
|
||||
|
||||
// Increase font size
|
||||
function enlargeFontSize(e) {
|
||||
e.preventDefault();
|
||||
if (fontState.size >= MAX_SIZE) return;
|
||||
|
||||
fontState.size++;
|
||||
saveFontSettings();
|
||||
}
|
||||
|
||||
// Decrease font size
|
||||
function reduceFontSize(e) {
|
||||
e.preventDefault();
|
||||
if (fontState.size <= MIN_SIZE) return;
|
||||
|
||||
fontState.size--;
|
||||
saveFontSettings();
|
||||
}
|
||||
|
||||
// Change font family
|
||||
function changeFontFamily(configName, e) {
|
||||
if (e && e instanceof Event) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
var familyId = getFontFamilyId(configName);
|
||||
fontState.family = familyId;
|
||||
saveFontSettings();
|
||||
}
|
||||
|
||||
// Change type of color theme
|
||||
function changeColorTheme(configName, e) {
|
||||
if (e && e instanceof Event) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
var $book = gitbook.state.$book;
|
||||
|
||||
// Remove currently applied color theme
|
||||
if (fontState.theme !== 0)
|
||||
$book.removeClass('color-theme-'+fontState.theme);
|
||||
|
||||
// Set new color theme
|
||||
var themeId = getThemeId(configName);
|
||||
fontState.theme = themeId;
|
||||
if (fontState.theme !== 0)
|
||||
$book.addClass('color-theme-'+fontState.theme);
|
||||
|
||||
saveFontSettings();
|
||||
}
|
||||
|
||||
// Return the correct id for a font-family config key
|
||||
// Default to first font-family
|
||||
function getFontFamilyId(configName) {
|
||||
// Search for plugin configured font family
|
||||
var configFamily = $.grep(FAMILIES, function(family) {
|
||||
return family.config == configName;
|
||||
})[0];
|
||||
// Fallback to default font family
|
||||
return (!!configFamily)? configFamily.id : 0;
|
||||
}
|
||||
|
||||
// Return the correct id for a theme config key
|
||||
// Default to first theme
|
||||
function getThemeId(configName) {
|
||||
// Search for plugin configured theme
|
||||
var configTheme = $.grep(THEMES, function(theme) {
|
||||
return theme.config == configName;
|
||||
})[0];
|
||||
// Fallback to default theme
|
||||
return (!!configTheme)? configTheme.id : 0;
|
||||
}
|
||||
|
||||
function update() {
|
||||
var $book = gitbook.state.$book;
|
||||
|
||||
$('.font-settings .font-family-list li').removeClass('active');
|
||||
$('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active');
|
||||
|
||||
$book[0].className = $book[0].className.replace(/\bfont-\S+/g, '');
|
||||
$book.addClass('font-size-'+fontState.size);
|
||||
$book.addClass('font-family-'+fontState.family);
|
||||
|
||||
if(fontState.theme !== 0) {
|
||||
$book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, '');
|
||||
$book.addClass('color-theme-'+fontState.theme);
|
||||
}
|
||||
}
|
||||
|
||||
function init(config) {
|
||||
// Search for plugin configured font family
|
||||
var configFamily = getFontFamilyId(config.family),
|
||||
configTheme = getThemeId(config.theme);
|
||||
|
||||
// Instantiate font state object
|
||||
fontState = gitbook.storage.get('fontState', {
|
||||
size: config.size || 2,
|
||||
family: configFamily,
|
||||
theme: configTheme
|
||||
});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
// Remove existing fontsettings buttons
|
||||
if (!!BUTTON_ID) {
|
||||
gitbook.toolbar.removeButton(BUTTON_ID);
|
||||
}
|
||||
|
||||
// Create buttons in toolbar
|
||||
BUTTON_ID = gitbook.toolbar.createButton({
|
||||
icon: 'fa fa-font',
|
||||
label: 'Font Settings',
|
||||
className: 'font-settings',
|
||||
dropdown: [
|
||||
[
|
||||
{
|
||||
text: 'A',
|
||||
className: 'font-reduce',
|
||||
onClick: reduceFontSize
|
||||
},
|
||||
{
|
||||
text: 'A',
|
||||
className: 'font-enlarge',
|
||||
onClick: enlargeFontSize
|
||||
}
|
||||
],
|
||||
$.map(FAMILIES, function(family) {
|
||||
family.onClick = function(e) {
|
||||
return changeFontFamily(family.config, e);
|
||||
};
|
||||
|
||||
return family;
|
||||
}),
|
||||
$.map(THEMES, function(theme) {
|
||||
theme.onClick = function(e) {
|
||||
return changeColorTheme(theme.config, e);
|
||||
};
|
||||
|
||||
return theme;
|
||||
})
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// Init configuration at start
|
||||
gitbook.events.bind('start', function(e, config) {
|
||||
var opts = config.fontsettings;
|
||||
|
||||
// Generate buttons at start
|
||||
updateButtons();
|
||||
|
||||
// Init current settings
|
||||
init(opts);
|
||||
});
|
||||
|
||||
// Expose API
|
||||
gitbook.fontsettings = {
|
||||
enlargeFontSize: enlargeFontSize,
|
||||
reduceFontSize: reduceFontSize,
|
||||
setTheme: changeColorTheme,
|
||||
setFamily: changeFontFamily,
|
||||
getThemes: getThemes,
|
||||
setThemes: setThemes,
|
||||
getFamilies: getFamilies,
|
||||
setFamilies: setFamilies
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
135
docs/_book/gitbook/gitbook-plugin-highlight/ebook 2.css
Normal file
135
docs/_book/gitbook/gitbook-plugin-highlight/ebook 2.css
Normal file
@@ -0,0 +1,135 @@
|
||||
pre,
|
||||
code {
|
||||
/* http://jmblog.github.io/color-themes-for-highlightjs */
|
||||
/* Tomorrow Comment */
|
||||
/* Tomorrow Red */
|
||||
/* Tomorrow Orange */
|
||||
/* Tomorrow Yellow */
|
||||
/* Tomorrow Green */
|
||||
/* Tomorrow Aqua */
|
||||
/* Tomorrow Blue */
|
||||
/* Tomorrow Purple */
|
||||
}
|
||||
pre .hljs-comment,
|
||||
code .hljs-comment,
|
||||
pre .hljs-title,
|
||||
code .hljs-title {
|
||||
color: #8e908c;
|
||||
}
|
||||
pre .hljs-variable,
|
||||
code .hljs-variable,
|
||||
pre .hljs-attribute,
|
||||
code .hljs-attribute,
|
||||
pre .hljs-tag,
|
||||
code .hljs-tag,
|
||||
pre .hljs-regexp,
|
||||
code .hljs-regexp,
|
||||
pre .hljs-deletion,
|
||||
code .hljs-deletion,
|
||||
pre .ruby .hljs-constant,
|
||||
code .ruby .hljs-constant,
|
||||
pre .xml .hljs-tag .hljs-title,
|
||||
code .xml .hljs-tag .hljs-title,
|
||||
pre .xml .hljs-pi,
|
||||
code .xml .hljs-pi,
|
||||
pre .xml .hljs-doctype,
|
||||
code .xml .hljs-doctype,
|
||||
pre .html .hljs-doctype,
|
||||
code .html .hljs-doctype,
|
||||
pre .css .hljs-id,
|
||||
code .css .hljs-id,
|
||||
pre .css .hljs-class,
|
||||
code .css .hljs-class,
|
||||
pre .css .hljs-pseudo,
|
||||
code .css .hljs-pseudo {
|
||||
color: #c82829;
|
||||
}
|
||||
pre .hljs-number,
|
||||
code .hljs-number,
|
||||
pre .hljs-preprocessor,
|
||||
code .hljs-preprocessor,
|
||||
pre .hljs-pragma,
|
||||
code .hljs-pragma,
|
||||
pre .hljs-built_in,
|
||||
code .hljs-built_in,
|
||||
pre .hljs-literal,
|
||||
code .hljs-literal,
|
||||
pre .hljs-params,
|
||||
code .hljs-params,
|
||||
pre .hljs-constant,
|
||||
code .hljs-constant {
|
||||
color: #f5871f;
|
||||
}
|
||||
pre .ruby .hljs-class .hljs-title,
|
||||
code .ruby .hljs-class .hljs-title,
|
||||
pre .css .hljs-rules .hljs-attribute,
|
||||
code .css .hljs-rules .hljs-attribute {
|
||||
color: #eab700;
|
||||
}
|
||||
pre .hljs-string,
|
||||
code .hljs-string,
|
||||
pre .hljs-value,
|
||||
code .hljs-value,
|
||||
pre .hljs-inheritance,
|
||||
code .hljs-inheritance,
|
||||
pre .hljs-header,
|
||||
code .hljs-header,
|
||||
pre .hljs-addition,
|
||||
code .hljs-addition,
|
||||
pre .ruby .hljs-symbol,
|
||||
code .ruby .hljs-symbol,
|
||||
pre .xml .hljs-cdata,
|
||||
code .xml .hljs-cdata {
|
||||
color: #718c00;
|
||||
}
|
||||
pre .css .hljs-hexcolor,
|
||||
code .css .hljs-hexcolor {
|
||||
color: #3e999f;
|
||||
}
|
||||
pre .hljs-function,
|
||||
code .hljs-function,
|
||||
pre .python .hljs-decorator,
|
||||
code .python .hljs-decorator,
|
||||
pre .python .hljs-title,
|
||||
code .python .hljs-title,
|
||||
pre .ruby .hljs-function .hljs-title,
|
||||
code .ruby .hljs-function .hljs-title,
|
||||
pre .ruby .hljs-title .hljs-keyword,
|
||||
code .ruby .hljs-title .hljs-keyword,
|
||||
pre .perl .hljs-sub,
|
||||
code .perl .hljs-sub,
|
||||
pre .javascript .hljs-title,
|
||||
code .javascript .hljs-title,
|
||||
pre .coffeescript .hljs-title,
|
||||
code .coffeescript .hljs-title {
|
||||
color: #4271ae;
|
||||
}
|
||||
pre .hljs-keyword,
|
||||
code .hljs-keyword,
|
||||
pre .javascript .hljs-function,
|
||||
code .javascript .hljs-function {
|
||||
color: #8959a8;
|
||||
}
|
||||
pre .hljs,
|
||||
code .hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
color: #4d4d4c;
|
||||
padding: 0.5em;
|
||||
}
|
||||
pre .coffeescript .javascript,
|
||||
code .coffeescript .javascript,
|
||||
pre .javascript .xml,
|
||||
code .javascript .xml,
|
||||
pre .tex .hljs-formula,
|
||||
code .tex .hljs-formula,
|
||||
pre .xml .javascript,
|
||||
code .xml .javascript,
|
||||
pre .xml .vbscript,
|
||||
code .xml .vbscript,
|
||||
pre .xml .css,
|
||||
code .xml .css,
|
||||
pre .xml .hljs-cdata,
|
||||
code .xml .hljs-cdata {
|
||||
opacity: 0.5;
|
||||
}
|
||||
7
docs/_book/gitbook/gitbook-plugin-lunr/lunr.min 2.js
Normal file
7
docs/_book/gitbook/gitbook-plugin-lunr/lunr.min 2.js
Normal file
File diff suppressed because one or more lines are too long
7
docs/_book/gitbook/gitbook-plugin-search/lunr.min 2.js
Normal file
7
docs/_book/gitbook/gitbook-plugin-search/lunr.min 2.js
Normal file
File diff suppressed because one or more lines are too long
90
docs/_book/gitbook/gitbook-plugin-sharing/buttons 2.js
Normal file
90
docs/_book/gitbook/gitbook-plugin-sharing/buttons 2.js
Normal file
@@ -0,0 +1,90 @@
|
||||
require(['gitbook', 'jquery'], function(gitbook, $) {
|
||||
var SITES = {
|
||||
'facebook': {
|
||||
'label': 'Facebook',
|
||||
'icon': 'fa fa-facebook',
|
||||
'onClick': function(e) {
|
||||
e.preventDefault();
|
||||
window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href));
|
||||
}
|
||||
},
|
||||
'twitter': {
|
||||
'label': 'Twitter',
|
||||
'icon': 'fa fa-twitter',
|
||||
'onClick': function(e) {
|
||||
e.preventDefault();
|
||||
window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href));
|
||||
}
|
||||
},
|
||||
'google': {
|
||||
'label': 'Google+',
|
||||
'icon': 'fa fa-google-plus',
|
||||
'onClick': function(e) {
|
||||
e.preventDefault();
|
||||
window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href));
|
||||
}
|
||||
},
|
||||
'weibo': {
|
||||
'label': 'Weibo',
|
||||
'icon': 'fa fa-weibo',
|
||||
'onClick': function(e) {
|
||||
e.preventDefault();
|
||||
window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title));
|
||||
}
|
||||
},
|
||||
'instapaper': {
|
||||
'label': 'Instapaper',
|
||||
'icon': 'fa fa-instapaper',
|
||||
'onClick': function(e) {
|
||||
e.preventDefault();
|
||||
window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href));
|
||||
}
|
||||
},
|
||||
'vk': {
|
||||
'label': 'VK',
|
||||
'icon': 'fa fa-vk',
|
||||
'onClick': function(e) {
|
||||
e.preventDefault();
|
||||
window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
gitbook.events.bind('start', function(e, config) {
|
||||
var opts = config.sharing;
|
||||
|
||||
// Create dropdown menu
|
||||
var menu = $.map(opts.all, function(id) {
|
||||
var site = SITES[id];
|
||||
|
||||
return {
|
||||
text: site.label,
|
||||
onClick: site.onClick
|
||||
};
|
||||
});
|
||||
|
||||
// Create main button with dropdown
|
||||
if (menu.length > 0) {
|
||||
gitbook.toolbar.createButton({
|
||||
icon: 'fa fa-share-alt',
|
||||
label: 'Share',
|
||||
position: 'right',
|
||||
dropdown: [menu]
|
||||
});
|
||||
}
|
||||
|
||||
// Direct actions to share
|
||||
$.each(SITES, function(sideId, site) {
|
||||
if (!opts[sideId]) return;
|
||||
|
||||
gitbook.toolbar.createButton({
|
||||
icon: site.icon,
|
||||
label: site.text,
|
||||
position: 'right',
|
||||
onClick: site.onClick
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
BIN
docs/_book/gitbook/images/apple-touch-icon-precomposed-152 2.png
Normal file
BIN
docs/_book/gitbook/images/apple-touch-icon-precomposed-152 2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
BIN
docs/_book/img/crawlab-architecture 2.png
Normal file
BIN
docs/_book/img/crawlab-architecture 2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/img/crawlab-architecture 2.png
Normal file
BIN
docs/img/crawlab-architecture 2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
15537
frontend/package-lock.json
generated
Normal file
15537
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
frontend/src/api/login 2.js
Normal file
27
frontend/src/api/login 2.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function login (username, password) {
|
||||
return request({
|
||||
url: '/user/login',
|
||||
method: 'post',
|
||||
data: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getInfo (token) {
|
||||
return request({
|
||||
url: '/user/info',
|
||||
method: 'get',
|
||||
params: { token }
|
||||
})
|
||||
}
|
||||
|
||||
export function logout () {
|
||||
return request({
|
||||
url: '/user/logout',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
46
frontend/src/api/request 2.js
Normal file
46
frontend/src/api/request 2.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import axios from 'axios'
|
||||
|
||||
let baseUrl = 'http://localhost:8000/api'
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
baseUrl = 'http://139.129.230.98:8000/api'
|
||||
}
|
||||
// const baseUrl = process.env.API_BASE_URL || 'http://localhost:8000/api'
|
||||
|
||||
const request = (method, path, params, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = `${baseUrl}${path}`
|
||||
axios({
|
||||
method,
|
||||
url,
|
||||
params,
|
||||
data
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
const get = (path, params) => {
|
||||
return request('GET', path, params)
|
||||
}
|
||||
|
||||
const post = (path, data) => {
|
||||
return request('POST', path, {}, data)
|
||||
}
|
||||
|
||||
const put = (path, data) => {
|
||||
return request('PUT', path, {}, data)
|
||||
}
|
||||
|
||||
const del = (path, data) => {
|
||||
return request('DELETE', path)
|
||||
}
|
||||
|
||||
export default {
|
||||
baseUrl,
|
||||
request,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
delete: del
|
||||
}
|
||||
BIN
frontend/src/assets/404_images/404 2.png
Normal file
BIN
frontend/src/assets/404_images/404 2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
116
frontend/src/components/BackToTop/index 2.vue
Normal file
116
frontend/src/components/BackToTop/index 2.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<transition :name="transitionName">
|
||||
<div v-show="visible" :style="customStyle" class="back-to-ceiling" @click="backToTop">
|
||||
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height: 16px; width: 16px;">
|
||||
<title>回到顶部</title>
|
||||
<g>
|
||||
<path d="M12.036 15.59c0 .55-.453.995-.997.995H5.032c-.55 0-.997-.445-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29c.39-.39 1.026-.385 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BackToTop',
|
||||
props: {
|
||||
visibilityHeight: {
|
||||
type: Number,
|
||||
default: 400
|
||||
},
|
||||
backPosition: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
right: '50px',
|
||||
bottom: '50px',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
'border-radius': '4px',
|
||||
'line-height': '45px',
|
||||
background: '#e7eaf1'
|
||||
}
|
||||
}
|
||||
},
|
||||
transitionName: {
|
||||
type: String,
|
||||
default: 'fade'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
interval: null,
|
||||
isMoving: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleScroll() {
|
||||
this.visible = window.pageYOffset > this.visibilityHeight
|
||||
},
|
||||
backToTop() {
|
||||
if (this.isMoving) return
|
||||
const start = window.pageYOffset
|
||||
let i = 0
|
||||
this.isMoving = true
|
||||
this.interval = setInterval(() => {
|
||||
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
|
||||
if (next <= this.backPosition) {
|
||||
window.scrollTo(0, this.backPosition)
|
||||
clearInterval(this.interval)
|
||||
this.isMoving = false
|
||||
} else {
|
||||
window.scrollTo(0, next)
|
||||
}
|
||||
i++
|
||||
}, 16.7)
|
||||
},
|
||||
easeInOutQuad(t, b, c, d) {
|
||||
if ((t /= d / 2) < 1) return c / 2 * t * t + b
|
||||
return -c / 2 * (--t * (t - 2) - 1) + b
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.back-to-ceiling {
|
||||
position: fixed;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.back-to-ceiling:hover {
|
||||
background: #d5dbe7;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.back-to-ceiling .Icon {
|
||||
fill: #9aaabf;
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
86
frontend/src/components/Breadcrumb/index 2.vue
Normal file
86
frontend/src/components/Breadcrumb/index 2.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||
<span v-if="item.redirect==='noredirect'||index==levelList.length-1"
|
||||
class="no-redirect">{{$t(item.meta.title) }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ $t(item.meta.title) }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
levelList: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route () {
|
||||
this.getBreadcrumb()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
methods: {
|
||||
getBreadcrumb () {
|
||||
let matched = this.$route.matched.filter(item => item.name)
|
||||
|
||||
const first = matched[0]
|
||||
if (first && first.name !== 'Home') {
|
||||
matched = [{ path: '/home', meta: { title: 'Home' } }].concat(matched)
|
||||
}
|
||||
|
||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
},
|
||||
pathCompile (path) {
|
||||
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
||||
const { params } = this.$route
|
||||
var toPath = pathToRegexp.compile(path)
|
||||
return toPath(params)
|
||||
},
|
||||
handleLink (item) {
|
||||
const { redirect } = item
|
||||
if (redirect) {
|
||||
this.$router.push(redirect)
|
||||
return
|
||||
}
|
||||
this.$router.push(this.getGoToPath(item))
|
||||
},
|
||||
getGoToPath (item) {
|
||||
if (item.path) {
|
||||
var path = item.path
|
||||
var startPos = path.indexOf(':')
|
||||
|
||||
if (startPos !== -1) {
|
||||
var endPos = path.indexOf('/', startPos)
|
||||
var key = path.substring(startPos + 1, endPos)
|
||||
path = path.replace(':' + key, this.$route.params[key])
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
return item.redirect || item.path
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-left: 10px;
|
||||
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
156
frontend/src/components/Charts/keyboard 2.vue
Normal file
156
frontend/src/components/Charts/keyboard 2.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div :class="className" :id="id" :style="{height:height,width:width}"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
import resize from './mixins/resize'
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.getElementById(this.id))
|
||||
|
||||
const xAxisData = []
|
||||
const data = []
|
||||
const data2 = []
|
||||
for (let i = 0; i < 50; i++) {
|
||||
xAxisData.push(i)
|
||||
data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
|
||||
data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
|
||||
}
|
||||
this.chart.setOption(
|
||||
{
|
||||
backgroundColor: '#08263a',
|
||||
grid: {
|
||||
left: '5%',
|
||||
right: '5%'
|
||||
},
|
||||
xAxis: [{
|
||||
show: false,
|
||||
data: xAxisData
|
||||
}, {
|
||||
show: false,
|
||||
data: xAxisData
|
||||
}],
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: 50,
|
||||
dimension: 0,
|
||||
inRange: {
|
||||
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#4a657a'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#08263f'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'back',
|
||||
type: 'bar',
|
||||
data: data2,
|
||||
z: 1,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0.4,
|
||||
barBorderRadius: 5,
|
||||
shadowBlur: 3,
|
||||
shadowColor: '#111'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'Simulate Shadow',
|
||||
type: 'line',
|
||||
data,
|
||||
z: 2,
|
||||
showSymbol: false,
|
||||
animationDelay: 0,
|
||||
animationEasing: 'linear',
|
||||
animationDuration: 1200,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: 'transparent'
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: '#08263a',
|
||||
shadowBlur: 50,
|
||||
shadowColor: '#000'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'front',
|
||||
type: 'bar',
|
||||
data,
|
||||
xAxisIndex: 1,
|
||||
z: 3,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
barBorderRadius: 5
|
||||
}
|
||||
}
|
||||
}],
|
||||
animationEasing: 'elasticOut',
|
||||
animationEasingUpdate: 'elasticOut',
|
||||
animationDelay(idx) {
|
||||
return idx * 20
|
||||
},
|
||||
animationDelayUpdate(idx) {
|
||||
return idx * 20
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
32
frontend/src/components/Charts/mixins/resize 2.js
Normal file
32
frontend/src/components/Charts/mixins/resize 2.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { debounce } from '@/utils'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
sidebarElm: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.__resizeHandler = debounce(() => {
|
||||
if (this.chart) {
|
||||
this.chart.resize()
|
||||
}
|
||||
}, 100)
|
||||
window.addEventListener('resize', this.__resizeHandler)
|
||||
|
||||
this.sidebarElm = document.getElementsByClassName('sidebar-container')[0]
|
||||
this.sidebarElm && this.sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.__resizeHandler)
|
||||
|
||||
this.sidebarElm && this.sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler)
|
||||
},
|
||||
methods: {
|
||||
sidebarResizeHandler(e) {
|
||||
if (e.propertyName === 'width') {
|
||||
this.__resizeHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
162
frontend/src/components/Common/DialogView 2.vue
Normal file
162
frontend/src/components/Common/DialogView 2.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="dialog-view">
|
||||
<el-dialog
|
||||
class="deploy-dialog"
|
||||
:title="title"
|
||||
:visible.sync="dialogVisible"
|
||||
width="40%">
|
||||
<!--message-->
|
||||
<label>{{message}}</label>
|
||||
|
||||
<!--selection for node-->
|
||||
<el-select v-if="type === 'node'" v-model="activeSpider._id">
|
||||
<el-option v-for="op in spiderList" :key="op._id" :value="op._id" :label="op.name"></el-option>
|
||||
</el-select>
|
||||
|
||||
<!--selection for spider-->
|
||||
<el-select v-else-if="type === 'spider'" v-model="activeNode._id">
|
||||
<el-option v-for="op in nodeList" :key="op._id" :value="op._id" :label="op.name"></el-option>
|
||||
</el-select>
|
||||
|
||||
<!--action buttons-->
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="onCancel">Cancel</el-button>
|
||||
<el-button type="danger" @click="onConfirm">Confirm</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'DialogView',
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderList',
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('node', [
|
||||
'nodeList'
|
||||
]),
|
||||
...mapState('dialogView', [
|
||||
'dialogType'
|
||||
]),
|
||||
type () {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'node'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'node'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'spider'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'spider'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
activeNode: {
|
||||
get () {
|
||||
return this.$store.state.spider.activeNode
|
||||
},
|
||||
set () {
|
||||
this.$store.commit('spider/SET_ACTIVE_NODE')
|
||||
}
|
||||
},
|
||||
activeSpider: {
|
||||
get () {
|
||||
return this.$store.state.node.activeSpider
|
||||
},
|
||||
set () {
|
||||
this.$store.commit('node/SET_ACTIVE_SPIDER')
|
||||
}
|
||||
},
|
||||
dialogVisible: {
|
||||
get () {
|
||||
return this.$store.state.dialogView.dialogVisible
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', value)
|
||||
}
|
||||
},
|
||||
title () {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'Deploy'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'Run'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'Deploy'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'Run'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
message () {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'Please select spider you would like to deploy'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'Please select spider you would like to run'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'Please select node you would like to deploy'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'Please select node you would like to run'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCancel () {
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
},
|
||||
onConfirm () {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
this.$store.dispatch('spider/deploySpider', {
|
||||
id: this.spiderForm._id,
|
||||
nodeId: this.activeNode._id
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`Spider "${this.spiderForm.name}" has been deployed on node "${this.activeNode._id}" successfully`)
|
||||
})
|
||||
.finally(() => {
|
||||
// get spider deploys
|
||||
this.$store.dispatch('spider/getDeployList', this.$route.params.id)
|
||||
|
||||
// close dialog
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
})
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
this.$store.dispatch('spider/crawlSpider', this.spiderForm._id)
|
||||
.then(() => {
|
||||
this.$message.success(`Spider "${this.spiderForm.name}" started to run on node "${this.activeNode._id}"`)
|
||||
})
|
||||
.finally(() => {
|
||||
// get spider tasks
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('spider/getTaskList', this.$route.params.id)
|
||||
}, 500)
|
||||
|
||||
// close dialog
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
})
|
||||
} else {
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (!this.spiderList || !this.spiderList.length) this.$store.dispatch('spider/getSpiderList')
|
||||
if (!this.nodeList || !this.nodeList.length) this.$store.dispatch('node/getNodeList')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
157
frontend/src/components/DndList/index 2.vue
Normal file
157
frontend/src/components/DndList/index 2.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="dndList">
|
||||
<div :style="{width:width1}" class="dndList-list">
|
||||
<h3>{{ list1Title }}</h3>
|
||||
<draggable :list="list1" :options="{group:'article'}" class="dragArea">
|
||||
<div v-for="element in list1" :key="element.id" class="list-complete-item">
|
||||
<div class="list-complete-item-handle">{{ element.id }}[{{ element.author }}] {{ element.title }}</div>
|
||||
<div style="position:absolute;right:0px;">
|
||||
<span style="float: right ;margin-top: -20px;margin-right:5px;" @click="deleteEle(element)">
|
||||
<i style="color:#ff4949" class="el-icon-delete"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
<div :style="{width:width2}" class="dndList-list">
|
||||
<h3>{{ list2Title }}</h3>
|
||||
<draggable :list="list2" :options="{group:'article'}" class="dragArea">
|
||||
<div v-for="element in list2" :key="element.id" class="list-complete-item">
|
||||
<div class="list-complete-item-handle2" @click="pushEle(element)">{{ element.id }} [{{ element.author }}] {{ element.title }}</div>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
export default {
|
||||
name: 'DndList',
|
||||
components: { draggable },
|
||||
props: {
|
||||
list1: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
list2: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
list1Title: {
|
||||
type: String,
|
||||
default: 'list1'
|
||||
},
|
||||
list2Title: {
|
||||
type: String,
|
||||
default: 'list2'
|
||||
},
|
||||
width1: {
|
||||
type: String,
|
||||
default: '48%'
|
||||
},
|
||||
width2: {
|
||||
type: String,
|
||||
default: '48%'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isNotInList1(v) {
|
||||
return this.list1.every(k => v.id !== k.id)
|
||||
},
|
||||
isNotInList2(v) {
|
||||
return this.list2.every(k => v.id !== k.id)
|
||||
},
|
||||
deleteEle(ele) {
|
||||
for (const item of this.list1) {
|
||||
if (item.id === ele.id) {
|
||||
const index = this.list1.indexOf(item)
|
||||
this.list1.splice(index, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.isNotInList2(ele)) {
|
||||
this.list2.unshift(ele)
|
||||
}
|
||||
},
|
||||
pushEle(ele) {
|
||||
for (const item of this.list2) {
|
||||
if (item.id === ele.id) {
|
||||
const index = this.list2.indexOf(item)
|
||||
this.list2.splice(index, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.isNotInList1(ele)) {
|
||||
this.list1.push(ele)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.dndList {
|
||||
background: #fff;
|
||||
padding-bottom: 40px;
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
.dndList-list {
|
||||
float: left;
|
||||
padding-bottom: 30px;
|
||||
&:first-of-type {
|
||||
margin-right: 2%;
|
||||
}
|
||||
.dragArea {
|
||||
margin-top: 15px;
|
||||
min-height: 50px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-complete-item {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
padding: 5px 12px;
|
||||
margin-top: 4px;
|
||||
border: 1px solid #bfcbd9;
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
.list-complete-item-handle {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 50px;
|
||||
}
|
||||
|
||||
.list-complete-item-handle2 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.list-complete-item.sortable-chosen {
|
||||
background: #4AB7BD;
|
||||
}
|
||||
|
||||
.list-complete-item.sortable-ghost {
|
||||
background: #30B08F;
|
||||
}
|
||||
|
||||
.list-complete-enter,
|
||||
.list-complete-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
61
frontend/src/components/DragSelect/index 2.vue
Normal file
61
frontend/src/components/DragSelect/index 2.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-select ref="dragSelect" v-model="selectVal" v-bind="$attrs" class="drag-select" multiple v-on="$listeners">
|
||||
<slot/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
name: 'DragSelect',
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectVal: {
|
||||
get() {
|
||||
return [...this.value]
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', [...val])
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setSort()
|
||||
},
|
||||
methods: {
|
||||
setSort() {
|
||||
const el = this.$refs.dragSelect.$el.querySelectorAll('.el-select__tags > span')[0]
|
||||
this.sortable = Sortable.create(el, {
|
||||
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
|
||||
setData: function(dataTransfer) {
|
||||
dataTransfer.setData('Text', '')
|
||||
// to avoid Firefox bug
|
||||
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
|
||||
},
|
||||
onEnd: evt => {
|
||||
const targetRow = this.value.splice(evt.oldIndex, 1)[0]
|
||||
this.value.splice(evt.newIndex, 0, targetRow)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.drag-select >>> .sortable-ghost{
|
||||
opacity: .8;
|
||||
color: #fff!important;
|
||||
background: #42b983!important;
|
||||
}
|
||||
|
||||
.drag-select >>> .el-tag{
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
297
frontend/src/components/Dropzone/index 2.vue
Normal file
297
frontend/src/components/Dropzone/index 2.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div :ref="id" :action="url" :id="id" class="dropzone">
|
||||
<input type="file" name="file">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropzone from 'dropzone'
|
||||
import 'dropzone/dist/dropzone.css'
|
||||
// import { getToken } from 'api/qiniu';
|
||||
|
||||
Dropzone.autoDiscover = false
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
defaultMsg: {
|
||||
type: String,
|
||||
default: '上传图片'
|
||||
},
|
||||
acceptedFiles: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
thumbnailHeight: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
thumbnailWidth: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
showRemoveLink: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maxFilesize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
maxFiles: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
autoProcessQueue: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
useCustomDropzoneOptions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
defaultImg: {
|
||||
default: '',
|
||||
type: [String, Array]
|
||||
},
|
||||
couldPaste: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropzone: '',
|
||||
initOnce: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
defaultImg(val) {
|
||||
if (val.length === 0) {
|
||||
this.initOnce = false
|
||||
return
|
||||
}
|
||||
if (!this.initOnce) return
|
||||
this.initImages(val)
|
||||
this.initOnce = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const element = document.getElementById(this.id)
|
||||
const vm = this
|
||||
this.dropzone = new Dropzone(element, {
|
||||
clickable: this.clickable,
|
||||
thumbnailWidth: this.thumbnailWidth,
|
||||
thumbnailHeight: this.thumbnailHeight,
|
||||
maxFiles: this.maxFiles,
|
||||
maxFilesize: this.maxFilesize,
|
||||
dictRemoveFile: 'Remove',
|
||||
addRemoveLinks: this.showRemoveLink,
|
||||
acceptedFiles: this.acceptedFiles,
|
||||
autoProcessQueue: this.autoProcessQueue,
|
||||
dictDefaultMessage: '<i style="margin-top: 3em;display: inline-block" class="material-icons">' + this.defaultMsg + '</i><br>Drop files here to upload',
|
||||
dictMaxFilesExceeded: '只能一个图',
|
||||
previewTemplate: '<div class="dz-preview dz-file-preview"> <div class="dz-image" style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" ><img style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" data-dz-thumbnail /></div> <div class="dz-details"><div class="dz-size"><span data-dz-size></span></div> <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div> <div class="dz-error-message"><span data-dz-errormessage></span></div> <div class="dz-success-mark"> <i class="material-icons">done</i> </div> <div class="dz-error-mark"><i class="material-icons">error</i></div></div>',
|
||||
init() {
|
||||
const val = vm.defaultImg
|
||||
if (!val) return
|
||||
if (Array.isArray(val)) {
|
||||
if (val.length === 0) return
|
||||
val.map((v, i) => {
|
||||
const mockFile = { name: 'name' + i, size: 12345, url: v }
|
||||
this.options.addedfile.call(this, mockFile)
|
||||
this.options.thumbnail.call(this, mockFile, v)
|
||||
mockFile.previewElement.classList.add('dz-success')
|
||||
mockFile.previewElement.classList.add('dz-complete')
|
||||
vm.initOnce = false
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
const mockFile = { name: 'name', size: 12345, url: val }
|
||||
this.options.addedfile.call(this, mockFile)
|
||||
this.options.thumbnail.call(this, mockFile, val)
|
||||
mockFile.previewElement.classList.add('dz-success')
|
||||
mockFile.previewElement.classList.add('dz-complete')
|
||||
vm.initOnce = false
|
||||
}
|
||||
},
|
||||
accept: (file, done) => {
|
||||
/* 七牛*/
|
||||
// const token = this.$store.getters.token;
|
||||
// getToken(token).then(response => {
|
||||
// file.token = response.data.qiniu_token;
|
||||
// file.key = response.data.qiniu_key;
|
||||
// file.url = response.data.qiniu_url;
|
||||
// done();
|
||||
// })
|
||||
done()
|
||||
},
|
||||
sending: (file, xhr, formData) => {
|
||||
// formData.append('token', file.token);
|
||||
// formData.append('key', file.key);
|
||||
vm.initOnce = false
|
||||
}
|
||||
})
|
||||
|
||||
if (this.couldPaste) {
|
||||
document.addEventListener('paste', this.pasteImg)
|
||||
}
|
||||
|
||||
this.dropzone.on('success', file => {
|
||||
vm.$emit('dropzone-success', file, vm.dropzone.element)
|
||||
})
|
||||
this.dropzone.on('addedfile', file => {
|
||||
vm.$emit('dropzone-fileAdded', file)
|
||||
})
|
||||
this.dropzone.on('removedfile', file => {
|
||||
vm.$emit('dropzone-removedFile', file)
|
||||
})
|
||||
this.dropzone.on('error', (file, error, xhr) => {
|
||||
vm.$emit('dropzone-error', file, error, xhr)
|
||||
})
|
||||
this.dropzone.on('successmultiple', (file, error, xhr) => {
|
||||
vm.$emit('dropzone-successmultiple', file, error, xhr)
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('paste', this.pasteImg)
|
||||
this.dropzone.destroy()
|
||||
},
|
||||
methods: {
|
||||
removeAllFiles() {
|
||||
this.dropzone.removeAllFiles(true)
|
||||
},
|
||||
processQueue() {
|
||||
this.dropzone.processQueue()
|
||||
},
|
||||
pasteImg(event) {
|
||||
const items = (event.clipboardData || event.originalEvent.clipboardData).items
|
||||
if (items[0].kind === 'file') {
|
||||
this.dropzone.addFile(items[0].getAsFile())
|
||||
}
|
||||
},
|
||||
initImages(val) {
|
||||
if (!val) return
|
||||
if (Array.isArray(val)) {
|
||||
val.map((v, i) => {
|
||||
const mockFile = { name: 'name' + i, size: 12345, url: v }
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mockFile)
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, v)
|
||||
mockFile.previewElement.classList.add('dz-success')
|
||||
mockFile.previewElement.classList.add('dz-complete')
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
const mockFile = { name: 'name', size: 12345, url: val }
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mockFile)
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, val)
|
||||
mockFile.previewElement.classList.add('dz-success')
|
||||
mockFile.previewElement.classList.add('dz-complete')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dropzone {
|
||||
border: 2px solid #E5E5E5;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
color: #777;
|
||||
transition: background-color .2s linear;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dropzone:hover {
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
.dropzone .dz-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropzone input[name='file'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-image {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview:hover .dz-image img {
|
||||
transform: none;
|
||||
-webkit-filter: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details {
|
||||
bottom: 0px;
|
||||
top: 0px;
|
||||
color: white;
|
||||
background-color: rgba(33, 150, 243, 0.8);
|
||||
transition: opacity .2s linear;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename:hover span {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-remove {
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
color: white;
|
||||
margin-left: 15px;
|
||||
padding: 10px;
|
||||
top: inherit;
|
||||
bottom: 15px;
|
||||
border: 2px white solid;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.1px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview:hover .dz-remove {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
|
||||
margin-left: -40px;
|
||||
margin-top: -50px;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-success-mark i, .dropzone .dz-preview .dz-error-mark i {
|
||||
color: white;
|
||||
font-size: 5rem;
|
||||
}
|
||||
</style>
|
||||
63
frontend/src/components/ErrorLog/index 2.vue
Normal file
63
frontend/src/components/ErrorLog/index 2.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div v-if="errorLogs.length>0">
|
||||
<el-badge :is-dot="true" style="line-height: 25px;margin-top: -5px;" @click.native="dialogTableVisible=true">
|
||||
<el-button style="padding: 8px 10px;" size="small" type="danger">
|
||||
<svg-icon icon-class="bug" />
|
||||
</el-button>
|
||||
</el-badge>
|
||||
|
||||
<el-dialog :visible.sync="dialogTableVisible" title="Error Log" width="80%">
|
||||
<el-table :data="errorLogs" border>
|
||||
<el-table-column label="Message">
|
||||
<template slot-scope="scope">
|
||||
<div>
|
||||
<span class="message-title">Msg:</span>
|
||||
<el-tag type="danger">{{ scope.row.err.message }}</el-tag>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<span class="message-title" style="padding-right: 10px;">Info: </span>
|
||||
<el-tag type="warning">{{ scope.row.vm.$vnode.tag }} error in {{ scope.row.info }}</el-tag>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<span class="message-title" style="padding-right: 16px;">Url: </span>
|
||||
<el-tag type="success">{{ scope.row.url }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Stack">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.err.stack }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ErrorLog',
|
||||
data() {
|
||||
return {
|
||||
dialogTableVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
errorLogs() {
|
||||
return this.$store.getters.errorLogs
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-title {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
padding-right: 8px;
|
||||
}
|
||||
</style>
|
||||
200
frontend/src/components/FileList/FileList 2.vue
Normal file
200
frontend/src/components/FileList/FileList 2.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="file-list-container">
|
||||
<div class="top-part">
|
||||
<!--file path-->
|
||||
<div class="file-path-container">
|
||||
<div class="left">
|
||||
<i class="el-icon-back" @click="onBack"></i>
|
||||
<div class="file-path" v-show="!isEdit">{{currentPath}}</div>
|
||||
<el-input class="file-path"
|
||||
v-show="isEdit"
|
||||
v-model="currentPath"
|
||||
@change="onChange"
|
||||
@keypress.enter.native="onChangeSubmit">
|
||||
</el-input>
|
||||
</div>
|
||||
<i class="el-icon-edit" @click="onEdit"></i>
|
||||
</div>
|
||||
<!--action-->
|
||||
<div class="action-container">
|
||||
<el-button type="success" size="mini">{{$t('Choose Folder')}}</el-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--file list-->
|
||||
<template v-if="true">
|
||||
<!--<code-mirror v-model="code"/>-->
|
||||
<ul class="file-list">
|
||||
<li v-for="(item, index) in fileList" :key="index" class="item" @click="onItemClick(item)">
|
||||
<span class="item-icon">
|
||||
<i class="fa" :class="getIcon(item.type)"></i>
|
||||
</span>
|
||||
<span class="item-name">
|
||||
{{item.path}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template v-else>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import path from 'path'
|
||||
// import { codemirror } from 'vue-codemirror-lite'
|
||||
|
||||
export default {
|
||||
name: 'FileList',
|
||||
components: {
|
||||
// CodeMirror: codemirror
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
code: 'var hello = \'world\'',
|
||||
isEdit: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('file', [
|
||||
'fileList'
|
||||
]),
|
||||
currentPath: {
|
||||
set (value) {
|
||||
this.$store.commit('file/SET_CURRENT_PATH', value)
|
||||
},
|
||||
get () {
|
||||
return this.$store.state.file.currentPath
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getIcon (type) {
|
||||
if (type === 1) {
|
||||
return 'fa-file-o'
|
||||
} else if (type === 2) {
|
||||
return 'fa-folder'
|
||||
}
|
||||
},
|
||||
onEdit () {
|
||||
this.isEdit = true
|
||||
},
|
||||
onChange (path) {
|
||||
this.$store.commit('file/SET_CURRENT_PATH', path)
|
||||
},
|
||||
onChangeSubmit () {
|
||||
this.isEdit = false
|
||||
this.$store.dispatch('file/getFileList', this.currentPath)
|
||||
},
|
||||
onItemClick (item) {
|
||||
if (item.type === 2) {
|
||||
this.$store.commit('file/SET_CURRENT_PATH', path.join(this.currentPath, item.path))
|
||||
this.$store.dispatch('file/getFileList', this.currentPath)
|
||||
}
|
||||
},
|
||||
onBack () {
|
||||
const sep = '/'
|
||||
let arr = this.currentPath.split(sep)
|
||||
arr.splice(arr.length - 1, 1)
|
||||
const path = arr.join(sep)
|
||||
this.$store.commit('file/SET_CURRENT_PATH', path)
|
||||
this.$store.dispatch('file/getFileList', this.currentPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.file-list-container {
|
||||
height: 100%;
|
||||
|
||||
.top-part {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.file-path-container {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
margin: 0 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(48, 65, 86, 0.4);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.left {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.el-icon-back {
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
/*height: 22px;*/
|
||||
width: 100%;
|
||||
line-height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon-edit {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.action-container {
|
||||
text-align: right;
|
||||
padding: 1px 5px;
|
||||
height: 24px;
|
||||
|
||||
.el-button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-list {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
height: 450px;
|
||||
overflow-y: auto;
|
||||
|
||||
.item {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
color: #303133;
|
||||
|
||||
.item-icon {
|
||||
.fa-folder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background-color: rgba(48, 65, 86, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.file-path >>> .el-input__inner {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
height: 18px;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: 2px solid #409EFF;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-line {
|
||||
padding-right: 20px;
|
||||
}
|
||||
</style>
|
||||
51
frontend/src/components/GithubCorner/index 2.vue
Normal file
51
frontend/src/components/GithubCorner/index 2.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<a href="https://github.com/PanJiaChen/vue-element-admin" target="_blank" class="github-corner" aria-label="View source on Github">
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 250 250"
|
||||
style="fill:#40c9c6; color:#fff;"
|
||||
aria-hidden="true">
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"/>
|
||||
<path
|
||||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
||||
fill="currentColor"
|
||||
style="transform-origin: 130px 106px;"
|
||||
class="octo-arm"/>
|
||||
<path
|
||||
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
|
||||
fill="currentColor"
|
||||
class="octo-body"/>
|
||||
</svg>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
}
|
||||
|
||||
@keyframes octocat-wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
20%,
|
||||
60% {
|
||||
transform: rotate(-25deg)
|
||||
}
|
||||
40%,
|
||||
80% {
|
||||
transform: rotate(10deg)
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:500px) {
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: none
|
||||
}
|
||||
.github-corner .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
}
|
||||
}
|
||||
</style>
|
||||
42
frontend/src/components/Hamburger/index 2.vue
Normal file
42
frontend/src/components/Hamburger/index 2.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg
|
||||
:class="{'is-active':isActive}"
|
||||
class="hamburger"
|
||||
viewBox="0 0 1024 1024"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
@click="toggleClick">
|
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
toggleClick: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.hamburger.is-active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
187
frontend/src/components/HeaderSearch/index 2.vue
Normal file
187
frontend/src/components/HeaderSearch/index 2.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div :class="{'show':show}" class="header-search">
|
||||
<svg-icon class-name="search-icon" icon-class="search" @click="click" />
|
||||
<el-select
|
||||
ref="headerSearchSelect"
|
||||
v-model="search"
|
||||
:remote-method="querySearch"
|
||||
filterable
|
||||
default-first-option
|
||||
remote
|
||||
placeholder="Search"
|
||||
class="header-search-select"
|
||||
@change="change">
|
||||
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Fuse from 'fuse.js'
|
||||
import path from 'path'
|
||||
import i18n from '@/lang'
|
||||
|
||||
export default {
|
||||
name: 'HeaderSearch',
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
options: [],
|
||||
searchPool: [],
|
||||
show: false,
|
||||
fuse: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
routers() {
|
||||
return this.$store.getters.permission_routers
|
||||
},
|
||||
lang() {
|
||||
return this.$store.getters.language
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
lang() {
|
||||
this.searchPool = this.generateRouters(this.routers)
|
||||
},
|
||||
routers() {
|
||||
this.searchPool = this.generateRouters(this.routers)
|
||||
},
|
||||
searchPool(list) {
|
||||
this.initFuse(list)
|
||||
},
|
||||
show(value) {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', this.close)
|
||||
} else {
|
||||
document.body.removeEventListener('click', this.close)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.searchPool = this.generateRouters(this.routers)
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.show = !this.show
|
||||
if (this.show) {
|
||||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
|
||||
this.options = []
|
||||
this.show = false
|
||||
},
|
||||
change(val) {
|
||||
this.$router.push(val.path)
|
||||
this.search = ''
|
||||
this.options = []
|
||||
this.$nextTick(() => {
|
||||
this.show = false
|
||||
})
|
||||
},
|
||||
initFuse(list) {
|
||||
this.fuse = new Fuse(list, {
|
||||
shouldSort: true,
|
||||
threshold: 0.4,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 1,
|
||||
keys: [{
|
||||
name: 'title',
|
||||
weight: 0.7
|
||||
}, {
|
||||
name: 'path',
|
||||
weight: 0.3
|
||||
}]
|
||||
})
|
||||
},
|
||||
// Filter out the routes that can be displayed in the sidebar
|
||||
// And generate the internationalized title
|
||||
generateRouters(routers, basePath = '/', prefixTitle = []) {
|
||||
let res = []
|
||||
|
||||
for (const router of routers) {
|
||||
// skip hidden router
|
||||
if (router.hidden) { continue }
|
||||
|
||||
const data = {
|
||||
path: path.resolve(basePath, router.path),
|
||||
title: [...prefixTitle]
|
||||
}
|
||||
|
||||
if (router.meta && router.meta.title) {
|
||||
// generate internationalized title
|
||||
const i18ntitle = i18n.t(`route.${router.meta.title}`)
|
||||
|
||||
data.title = [...data.title, i18ntitle]
|
||||
|
||||
if (router.redirect !== 'noredirect') {
|
||||
// only push the routes with title
|
||||
// special case: need to exclude parent router without redirect
|
||||
res.push(data)
|
||||
}
|
||||
}
|
||||
|
||||
// recursive child routers
|
||||
if (router.children) {
|
||||
const tempRouters = this.generateRouters(router.children, data.path, data.title)
|
||||
if (tempRouters.length >= 1) {
|
||||
res = [...res, ...tempRouters]
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
querySearch(query) {
|
||||
if (query !== '') {
|
||||
this.options = this.fuse.search(query)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-search {
|
||||
font-size: 0 !important;
|
||||
|
||||
.search-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-search-select {
|
||||
font-size: 18px;
|
||||
transition: width 0.2s;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
/deep/ .el-input__inner {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&.show {
|
||||
.header-search-select {
|
||||
width: 210px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1420
frontend/src/components/ImageCropper/index 2.vue
Normal file
1420
frontend/src/components/ImageCropper/index 2.vue
Normal file
File diff suppressed because it is too large
Load Diff
19
frontend/src/components/ImageCropper/utils/data2blob 2.js
Executable file
19
frontend/src/components/ImageCropper/utils/data2blob 2.js
Executable file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* database64文件格式转换为2进制
|
||||
*
|
||||
* @param {[String]} data dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
|
||||
* @param {[String]} mime [description]
|
||||
* @return {[blob]} [description]
|
||||
*/
|
||||
export default function(data, mime) {
|
||||
data = data.split(',')[1]
|
||||
data = window.atob(data)
|
||||
var ia = new Uint8Array(data.length)
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
ia[i] = data.charCodeAt(i)
|
||||
}
|
||||
// canvas.toDataURL 返回的默认格式就是 image/png
|
||||
return new Blob([ia], {
|
||||
type: mime
|
||||
})
|
||||
}
|
||||
73
frontend/src/components/InfoView/NodeInfoView 2.vue
Normal file
73
frontend/src/components/InfoView/NodeInfoView 2.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="info-view">
|
||||
<el-row>
|
||||
<el-form label-width="150px"
|
||||
:model="nodeForm"
|
||||
ref="nodeForm"
|
||||
class="node-form"
|
||||
label-position="right">
|
||||
<el-form-item :label="$t('Node Name')">
|
||||
<el-input v-model="nodeForm.name" :placeholder="$t('Node Name')" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Node IP')" prop="ip" required>
|
||||
<el-input v-model="nodeForm.ip" :placeholder="$t('Node IP')" :disabled="isView"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Node Port')" prop="port" required>
|
||||
<el-input v-model="nodeForm.port" :placeholder="$t('Node Port')" :disabled="isView"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Description')">
|
||||
<el-input type="textarea" v-model="nodeForm.description" :placeholder="$t('Description')" :disabled="isView">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row class="button-container" v-if="!isView">
|
||||
<el-button type="success" @click="onSave">{{$t('Save')}}</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'NodeInfoView',
|
||||
props: {
|
||||
isView: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onSave () {
|
||||
this.$refs.nodeForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.$store.dispatch('node/editNode')
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('Node info has been saved successfully'))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.node-form {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
padding: 0 10px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
72
frontend/src/components/JsonEditor/index 2.vue
Normal file
72
frontend/src/components/JsonEditor/index 2.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="json-editor">
|
||||
<textarea ref="textarea"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror/addon/lint/lint.css'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import 'codemirror/theme/rubyblue.css'
|
||||
require('script-loader!jsonlint')
|
||||
import 'codemirror/mode/javascript/javascript'
|
||||
import 'codemirror/addon/lint/lint'
|
||||
import 'codemirror/addon/lint/json-lint'
|
||||
|
||||
export default {
|
||||
name: 'JsonEditor',
|
||||
/* eslint-disable vue/require-prop-types */
|
||||
props: ['value'],
|
||||
data() {
|
||||
return {
|
||||
jsonEditor: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(value) {
|
||||
const editor_value = this.jsonEditor.getValue()
|
||||
if (value !== editor_value) {
|
||||
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.jsonEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
|
||||
lineNumbers: true,
|
||||
mode: 'application/json',
|
||||
gutters: ['CodeMirror-lint-markers'],
|
||||
theme: 'rubyblue',
|
||||
lint: true
|
||||
})
|
||||
|
||||
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
|
||||
this.jsonEditor.on('change', cm => {
|
||||
this.$emit('changed', cm.getValue())
|
||||
this.$emit('input', cm.getValue())
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
getValue() {
|
||||
return this.jsonEditor.getValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.json-editor{
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.json-editor >>> .CodeMirror {
|
||||
height: auto;
|
||||
min-height: 300px;
|
||||
}
|
||||
.json-editor >>> .CodeMirror-scroll{
|
||||
min-height: 300px;
|
||||
}
|
||||
.json-editor >>> .cm-s-rubyblue span.cm-string {
|
||||
color: #F08047;
|
||||
}
|
||||
</style>
|
||||
89
frontend/src/components/Kanban/index 2.vue
Normal file
89
frontend/src/components/Kanban/index 2.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="board-column">
|
||||
<div class="board-column-header">
|
||||
{{ headerText }}
|
||||
</div>
|
||||
<draggable
|
||||
:list="list"
|
||||
:options="options"
|
||||
class="board-column-content">
|
||||
<div v-for="element in list" :key="element.id" class="board-item">
|
||||
{{ element.name }} {{ element.id }}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
export default {
|
||||
name: 'DragKanbanDemo',
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
props: {
|
||||
headerText: {
|
||||
type: String,
|
||||
default: 'Header'
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.board-column {
|
||||
min-width: 300px;
|
||||
min-height: 100px;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
background: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
|
||||
.board-column-header {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
overflow: hidden;
|
||||
padding: 0 20px;
|
||||
text-align: center;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.board-column-content {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
border: 10px solid transparent;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.board-item {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
margin: 5px 0;
|
||||
background-color: #fff;
|
||||
text-align: left;
|
||||
line-height: 54px;
|
||||
padding: 5px 10px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 1px 3px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
32
frontend/src/components/LangSelect/index 2.vue
Normal file
32
frontend/src/components/LangSelect/index 2.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<el-dropdown trigger="click" class="international" @command="handleSetLanguage">
|
||||
<div>
|
||||
<svg-icon class-name="international-icon" icon-class="language" />
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item :disabled="language==='zh'" command="zh">中文</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="language==='en'" command="en">English</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="language==='es'" command="es">Español</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
language() {
|
||||
return this.$store.getters.language
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSetLanguage(lang) {
|
||||
this.$i18n.locale = lang
|
||||
this.$store.dispatch('setLanguage', lang)
|
||||
this.$message({
|
||||
message: 'Switch Language Success',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
354
frontend/src/components/MDinput/index 2.vue
Normal file
354
frontend/src/components/MDinput/index 2.vue
Normal file
@@ -0,0 +1,354 @@
|
||||
<template>
|
||||
<div :class="computedClasses" class="material-input__component">
|
||||
<div :class="{iconClass:icon}">
|
||||
<i v-if="icon" :class="['el-icon-' + icon]" class="el-input__icon material-input__icon"/>
|
||||
<input
|
||||
v-if="type === 'email'"
|
||||
:name="name"
|
||||
:placeholder="fillPlaceHolder"
|
||||
v-model="currentValue"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autoComplete="autoComplete"
|
||||
:required="required"
|
||||
type="email"
|
||||
class="material-input"
|
||||
@focus="handleMdFocus"
|
||||
@blur="handleMdBlur"
|
||||
@input="handleModelInput">
|
||||
<input
|
||||
v-if="type === 'url'"
|
||||
:name="name"
|
||||
:placeholder="fillPlaceHolder"
|
||||
v-model="currentValue"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autoComplete="autoComplete"
|
||||
:required="required"
|
||||
type="url"
|
||||
class="material-input"
|
||||
@focus="handleMdFocus"
|
||||
@blur="handleMdBlur"
|
||||
@input="handleModelInput">
|
||||
<input
|
||||
v-if="type === 'number'"
|
||||
:name="name"
|
||||
:placeholder="fillPlaceHolder"
|
||||
v-model="currentValue"
|
||||
:step="step"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autoComplete="autoComplete"
|
||||
:max="max"
|
||||
:min="min"
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength"
|
||||
:required="required"
|
||||
type="number"
|
||||
class="material-input"
|
||||
@focus="handleMdFocus"
|
||||
@blur="handleMdBlur"
|
||||
@input="handleModelInput">
|
||||
<input
|
||||
v-if="type === 'password'"
|
||||
:name="name"
|
||||
:placeholder="fillPlaceHolder"
|
||||
v-model="currentValue"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autoComplete="autoComplete"
|
||||
:max="max"
|
||||
:min="min"
|
||||
:required="required"
|
||||
type="password"
|
||||
class="material-input"
|
||||
@focus="handleMdFocus"
|
||||
@blur="handleMdBlur"
|
||||
@input="handleModelInput">
|
||||
<input
|
||||
v-if="type === 'tel'"
|
||||
:name="name"
|
||||
:placeholder="fillPlaceHolder"
|
||||
v-model="currentValue"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autoComplete="autoComplete"
|
||||
:required="required"
|
||||
type="tel"
|
||||
class="material-input"
|
||||
@focus="handleMdFocus"
|
||||
@blur="handleMdBlur"
|
||||
@input="handleModelInput">
|
||||
<input
|
||||
v-if="type === 'text'"
|
||||
:name="name"
|
||||
:placeholder="fillPlaceHolder"
|
||||
v-model="currentValue"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autoComplete="autoComplete"
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength"
|
||||
:required="required"
|
||||
type="text"
|
||||
class="material-input"
|
||||
@focus="handleMdFocus"
|
||||
@blur="handleMdBlur"
|
||||
@input="handleModelInput">
|
||||
<span class="material-input-bar"/>
|
||||
<label class="material-label">
|
||||
<slot/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
|
||||
|
||||
export default {
|
||||
name: 'MdInput',
|
||||
props: {
|
||||
/* eslint-disable */
|
||||
icon: String,
|
||||
name: String,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
value: [String, Number],
|
||||
placeholder: String,
|
||||
readonly: Boolean,
|
||||
disabled: Boolean,
|
||||
min: String,
|
||||
max: String,
|
||||
step: String,
|
||||
minlength: Number,
|
||||
maxlength: Number,
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoComplete: {
|
||||
type: String,
|
||||
default: 'off'
|
||||
},
|
||||
validateEvent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.value,
|
||||
focus: false,
|
||||
fillPlaceHolder: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
computedClasses() {
|
||||
return {
|
||||
'material--active': this.focus,
|
||||
'material--disabled': this.disabled,
|
||||
'material--raised': Boolean(this.focus || this.currentValue) // has value
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.currentValue = newValue
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleModelInput(event) {
|
||||
const value = event.target.value
|
||||
this.$emit('input', value)
|
||||
if (this.$parent.$options.componentName === 'ElFormItem') {
|
||||
if (this.validateEvent) {
|
||||
this.$parent.$emit('el.form.change', [value])
|
||||
}
|
||||
}
|
||||
this.$emit('change', value)
|
||||
},
|
||||
handleMdFocus(event) {
|
||||
this.focus = true
|
||||
this.$emit('focus', event)
|
||||
if (this.placeholder && this.placeholder !== '') {
|
||||
this.fillPlaceHolder = this.placeholder
|
||||
}
|
||||
},
|
||||
handleMdBlur(event) {
|
||||
this.focus = false
|
||||
this.$emit('blur', event)
|
||||
this.fillPlaceHolder = null
|
||||
if (this.$parent.$options.componentName === 'ElFormItem') {
|
||||
if (this.validateEvent) {
|
||||
this.$parent.$emit('el.form.blur', [this.currentValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
// Fonts:
|
||||
$font-size-base: 16px;
|
||||
$font-size-small: 18px;
|
||||
$font-size-smallest: 12px;
|
||||
$font-weight-normal: normal;
|
||||
$font-weight-bold: bold;
|
||||
$apixel: 1px;
|
||||
// Utils
|
||||
$spacer: 12px;
|
||||
$transition: 0.2s ease all;
|
||||
$index: 0px;
|
||||
$index-has-icon: 30px;
|
||||
// Theme:
|
||||
$color-white: white;
|
||||
$color-grey: #9E9E9E;
|
||||
$color-grey-light: #E0E0E0;
|
||||
$color-blue: #2196F3;
|
||||
$color-red: #F44336;
|
||||
$color-black: black;
|
||||
// Base clases:
|
||||
%base-bar-pseudo {
|
||||
content: '';
|
||||
height: 1px;
|
||||
width: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
transition: $transition;
|
||||
}
|
||||
|
||||
// Mixins:
|
||||
@mixin slided-top() {
|
||||
top: - ($font-size-base + $spacer);
|
||||
left: 0;
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
// Component:
|
||||
.material-input__component {
|
||||
margin-top: 36px;
|
||||
position: relative;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.iconClass {
|
||||
.material-input__icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
line-height: $font-size-base;
|
||||
color: $color-blue;
|
||||
top: $spacer;
|
||||
width: $index-has-icon;
|
||||
height: $font-size-base;
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-normal;
|
||||
pointer-events: none;
|
||||
}
|
||||
.material-label {
|
||||
left: $index-has-icon;
|
||||
}
|
||||
.material-input {
|
||||
text-indent: $index-has-icon;
|
||||
}
|
||||
}
|
||||
.material-input {
|
||||
font-size: $font-size-base;
|
||||
padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
border-radius: 0;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid transparent; // fixes the height issue
|
||||
}
|
||||
}
|
||||
.material-label {
|
||||
font-weight: $font-weight-normal;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
left: $index;
|
||||
top: 0;
|
||||
transition: $transition;
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
.material-input-bar {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
&:before {
|
||||
@extend %base-bar-pseudo;
|
||||
left: 50%;
|
||||
}
|
||||
&:after {
|
||||
@extend %base-bar-pseudo;
|
||||
right: 50%;
|
||||
}
|
||||
}
|
||||
// Disabled state:
|
||||
&.material--disabled {
|
||||
.material-input {
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
}
|
||||
// Raised state:
|
||||
&.material--raised {
|
||||
.material-label {
|
||||
@include slided-top();
|
||||
}
|
||||
}
|
||||
// Active state:
|
||||
&.material--active {
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.material-input__component {
|
||||
background: $color-white;
|
||||
.material-input {
|
||||
background: none;
|
||||
color: $color-black;
|
||||
text-indent: $index;
|
||||
border-bottom: 1px solid $color-grey-light;
|
||||
}
|
||||
.material-label {
|
||||
color: $color-grey;
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: $color-blue;
|
||||
}
|
||||
}
|
||||
// Active state:
|
||||
&.material--active {
|
||||
.material-label {
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
// Errors:
|
||||
&.material--has-errors {
|
||||
&.material--active .material-label {
|
||||
color: $color-red;
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
31
frontend/src/components/MarkdownEditor/defaultOptions 2.js
Normal file
31
frontend/src/components/MarkdownEditor/defaultOptions 2.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// doc: https://nhnent.github.io/tui.editor/api/latest/ToastUIEditor.html#ToastUIEditor
|
||||
export default {
|
||||
minHeight: '200px',
|
||||
previewStyle: 'vertical',
|
||||
useCommandShortcut: true,
|
||||
useDefaultHTMLSanitizer: true,
|
||||
usageStatistics: false,
|
||||
hideModeSwitch: false,
|
||||
toolbarItems: [
|
||||
'heading',
|
||||
'bold',
|
||||
'italic',
|
||||
'strike',
|
||||
'divider',
|
||||
'hr',
|
||||
'quote',
|
||||
'divider',
|
||||
'ul',
|
||||
'ol',
|
||||
'task',
|
||||
'indent',
|
||||
'outdent',
|
||||
'divider',
|
||||
'table',
|
||||
'image',
|
||||
'link',
|
||||
'divider',
|
||||
'code',
|
||||
'codeblock'
|
||||
]
|
||||
}
|
||||
55
frontend/src/components/Overview/NodeOverview 2.vue
Normal file
55
frontend/src/components/Overview/NodeOverview 2.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<!--last tasks-->
|
||||
<el-row>
|
||||
<task-table-view :title="$t('Latest Tasks')"/>
|
||||
</el-row>
|
||||
|
||||
<!--last deploys-->
|
||||
<el-row v-if="false">
|
||||
<deploy-table-view :title="$t('Latest Deploys')"/>
|
||||
</el-row>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<!--basic info-->
|
||||
<node-info-view/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import DeployTableView from '../TableView/DeployTableView'
|
||||
import TaskTableView from '../TableView/TaskTableView'
|
||||
import NodeInfoView from '../InfoView/NodeInfoView'
|
||||
|
||||
export default {
|
||||
name: 'NodeOverview',
|
||||
components: {
|
||||
NodeInfoView,
|
||||
DeployTableView,
|
||||
TaskTableView
|
||||
},
|
||||
computed: {
|
||||
id () {
|
||||
return this.$route.params.id
|
||||
},
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
])
|
||||
},
|
||||
methods: {},
|
||||
created () {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
margin: 10px 0 3px 0;
|
||||
}
|
||||
</style>
|
||||
100
frontend/src/components/Pagination/index 2.vue
Normal file
100
frontend/src/components/Pagination/index 2.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div :class="{'hidden':hidden}" class="pagination-container">
|
||||
<el-pagination
|
||||
:background="background"
|
||||
:current-page.sync="currentPage"
|
||||
:page-size.sync="pageSize"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
v-bind="$attrs"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { scrollTo } from '@/utils/scrollTo'
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
props: {
|
||||
total: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentPage: {
|
||||
get() {
|
||||
return this.page
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:page', val)
|
||||
}
|
||||
},
|
||||
pageSize: {
|
||||
get() {
|
||||
return this.limit
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:limit', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(val) {
|
||||
this.$emit('pagination', { page: this.currentPage, limit: val })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.$emit('pagination', { page: val, limit: this.pageSize })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
140
frontend/src/components/PanThumb/index 2.vue
Normal file
140
frontend/src/components/PanThumb/index 2.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
|
||||
<div class="pan-info">
|
||||
<div class="pan-info-roles-container">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
<img :src="image" class="pan-thumb">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PanThumb',
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pan-item {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.pan-info-roles-container {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pan-thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
transform-origin: 95% 40%;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.pan-thumb:after {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
top: 40%;
|
||||
left: 95%;
|
||||
margin: -4px 0 0 -4px;
|
||||
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.pan-info {
|
||||
position: absolute;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.pan-info h3 {
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
letter-spacing: 2px;
|
||||
font-size: 18px;
|
||||
margin: 0 60px;
|
||||
padding: 22px 0 0 0;
|
||||
height: 85px;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.pan-info p {
|
||||
color: #fff;
|
||||
padding: 10px 5px;
|
||||
font-style: italic;
|
||||
margin: 0 30px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.pan-info p a {
|
||||
display: block;
|
||||
color: #333;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
font-size: 9px;
|
||||
letter-spacing: 1px;
|
||||
padding-top: 24px;
|
||||
margin: 7px auto 0;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
opacity: 0;
|
||||
transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
|
||||
transform: translateX(60px) rotate(90deg);
|
||||
}
|
||||
|
||||
.pan-info p a:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.pan-item:hover .pan-thumb {
|
||||
transform: rotate(-110deg);
|
||||
}
|
||||
|
||||
.pan-item:hover .pan-info p a {
|
||||
opacity: 1;
|
||||
transform: translateX(0px) rotate(0deg);
|
||||
}
|
||||
</style>
|
||||
51
frontend/src/components/Screenfull/index 2.vue
Normal file
51
frontend/src/components/Screenfull/index 2.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import screenfull from 'screenfull'
|
||||
|
||||
export default {
|
||||
name: 'Screenfull',
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!screenfull.enabled) {
|
||||
this.$message({
|
||||
message: 'you browser can not work',
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
}
|
||||
screenfull.toggle()
|
||||
},
|
||||
init() {
|
||||
if (screenfull.enabled) {
|
||||
screenfull.on('change', () => {
|
||||
this.isFullscreen = screenfull.isFullscreen
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.screenfull-svg {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
fill: #5a5e66;;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: 10px;
|
||||
}
|
||||
</style>
|
||||
81
frontend/src/components/ScrollPane/index 2.vue
Normal file
81
frontend/src/components/ScrollPane/index 2.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
|
||||
<slot/>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const tagAndTagSpacing = 4 // tagAndTagSpacing
|
||||
|
||||
export default {
|
||||
name: 'ScrollPane',
|
||||
data () {
|
||||
return {
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleScroll (e) {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
||||
},
|
||||
moveToTarget (currentTag) {
|
||||
const $container = this.$refs.scrollContainer.$el
|
||||
const $containerWidth = $container.offsetWidth
|
||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
||||
const tagList = this.$parent.$refs.tag
|
||||
|
||||
let firstTag = null
|
||||
let lastTag = null
|
||||
|
||||
// find first tag and last tag
|
||||
if (tagList.length > 0) {
|
||||
firstTag = tagList[0]
|
||||
lastTag = tagList[tagList.length - 1]
|
||||
}
|
||||
|
||||
if (firstTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = 0
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
|
||||
} else {
|
||||
// find preTag and nextTag
|
||||
const currentIndex = tagList.findIndex(item => item === currentTag)
|
||||
const prevTag = tagList[currentIndex - 1]
|
||||
const nextTag = tagList[currentIndex + 1]
|
||||
// the tag's offsetLeft after of nextTag
|
||||
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
|
||||
|
||||
// the tag's offsetLeft before of prevTag
|
||||
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
|
||||
|
||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
|
||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.scroll-container {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
/deep/ {
|
||||
.el-scrollbar__bar {
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.el-scrollbar__wrap {
|
||||
height: 49px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
frontend/src/components/Share/dropdownMenu 2.vue
Normal file
100
frontend/src/components/Share/dropdownMenu 2.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div :class="{active:isActive}" class="share-dropdown-menu">
|
||||
<div class="share-dropdown-menu-wrapper">
|
||||
<span class="share-dropdown-menu-title" @click.self="clickTitle">{{ title }}</span>
|
||||
<div v-for="(item,index) of items" :key="index" class="share-dropdown-menu-item">
|
||||
<a v-if="item.href" :href="item.href" target="_blank">{{ item.title }}</a>
|
||||
<span v-else>{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'vue'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickTitle() {
|
||||
this.isActive = !this.isActive
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" >
|
||||
$n: 8; //和items.length 相同
|
||||
$t: .1s;
|
||||
.share-dropdown-menu {
|
||||
width: 250px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
&-title {
|
||||
width: 100%;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
background: black;
|
||||
color: white;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
z-index: 2;
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
&-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
&-item {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
background: #e0e0e0;
|
||||
line-height: 60px;
|
||||
height: 60px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
opacity: 1;
|
||||
transition: transform 0.28s ease;
|
||||
&:hover {
|
||||
background: black;
|
||||
color: white;
|
||||
}
|
||||
@for $i from 1 through $n {
|
||||
&:nth-of-type(#{$i}) {
|
||||
z-index: -1;
|
||||
transition-delay: $i*$t;
|
||||
transform: translate3d(0, -60px, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
.share-dropdown-menu-wrapper {
|
||||
z-index: 1;
|
||||
}
|
||||
.share-dropdown-menu-item {
|
||||
@for $i from 1 through $n {
|
||||
&:nth-of-type(#{$i}) {
|
||||
transition-delay: ($n - $i)*$t;
|
||||
transform: translate3d(0, ($i - 1)*60px, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
55
frontend/src/components/SizeSelect/index 2.vue
Normal file
55
frontend/src/components/SizeSelect/index 2.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<el-dropdown trigger="click" @command="handleSetSize">
|
||||
<div>
|
||||
<svg-icon class-name="size-icon" icon-class="size" />
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">{{
|
||||
item.label }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
sizeOptions: [
|
||||
{ label: 'Default', value: 'default' },
|
||||
{ label: 'Medium', value: 'medium' },
|
||||
{ label: 'Small', value: 'small' },
|
||||
{ label: 'Mini', value: 'mini' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
size() {
|
||||
return this.$store.getters.size
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSetSize(size) {
|
||||
this.$ELEMENT.size = size
|
||||
this.$store.dispatch('setSize', size)
|
||||
this.refreshView()
|
||||
this.$message({
|
||||
message: 'Switch Size Success',
|
||||
type: 'success'
|
||||
})
|
||||
},
|
||||
refreshView() {
|
||||
// In order to make the cached page re-rendered
|
||||
this.$store.dispatch('delAllCachedViews', this.$route)
|
||||
|
||||
const { fullPath } = this.$route
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
88
frontend/src/components/Sticky/index 2.vue
Normal file
88
frontend/src/components/Sticky/index 2.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div :style="{height:height+'px',zIndex:zIndex}">
|
||||
<div :class="className" :style="{top:stickyTop+'px',zIndex:zIndex,position:position,width:width,height:height+'px'}">
|
||||
<slot>
|
||||
<div>sticky</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Sticky',
|
||||
props: {
|
||||
stickyTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
position: '',
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
isSticky: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.height = this.$el.getBoundingClientRect().height
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
window.addEventListener('resize', this.handleReize)
|
||||
},
|
||||
activated() {
|
||||
this.handleScroll()
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
window.removeEventListener('resize', this.handleReize)
|
||||
},
|
||||
methods: {
|
||||
sticky() {
|
||||
if (this.active) {
|
||||
return
|
||||
}
|
||||
this.position = 'fixed'
|
||||
this.active = true
|
||||
this.width = this.width + 'px'
|
||||
this.isSticky = true
|
||||
},
|
||||
handleReset() {
|
||||
if (!this.active) {
|
||||
return
|
||||
}
|
||||
this.reset()
|
||||
},
|
||||
reset() {
|
||||
this.position = ''
|
||||
this.width = 'auto'
|
||||
this.active = false
|
||||
this.isSticky = false
|
||||
},
|
||||
handleScroll() {
|
||||
const width = this.$el.getBoundingClientRect().width
|
||||
this.width = width || 'auto'
|
||||
const offsetTop = this.$el.getBoundingClientRect().top
|
||||
if (offsetTop < this.stickyTop) {
|
||||
this.sticky()
|
||||
return
|
||||
}
|
||||
this.handleReset()
|
||||
},
|
||||
handleReize() {
|
||||
if (this.isSticky) {
|
||||
this.width = this.$el.getBoundingClientRect().width + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
43
frontend/src/components/SvgIcon/index 2.vue
Normal file
43
frontend/src/components/SvgIcon/index 2.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||
<use :xlink:href="iconName"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconName () {
|
||||
return `#icon-${this.iconClass}`
|
||||
},
|
||||
svgClass () {
|
||||
if (this.className) {
|
||||
return 'svg-icon ' + this.className
|
||||
} else {
|
||||
return 'svg-icon'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
73
frontend/src/components/TableView/DeployTableView 2.vue
Normal file
73
frontend/src/components/TableView/DeployTableView 2.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="deploy-table-view">
|
||||
<el-row class="title-wrapper">
|
||||
<h5 class="title">{{title}}</h5>
|
||||
<el-button type="success" plain class="small-btn" size="mini" icon="fa fa-refresh" @click="onRefresh"></el-button>
|
||||
</el-row>
|
||||
<el-table border height="240px" :data="deployList">
|
||||
<el-table-column property="version" label="Ver" width="40" align="center"></el-table-column>
|
||||
<el-table-column property="node" label="Node" width="220" align="center">
|
||||
<template slot-scope="scope">
|
||||
<a class="a-tag" @click="onClickNode(scope.row)">{{scope.row.node_id}}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="spider_name" label="Spider" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<a class="a-tag" @click="onClickSpider(scope.row)">{{scope.row.spider_name}}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="finish_ts" label="Finish Time" width="auto" align="center"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'DeployTableView',
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('deploy', [
|
||||
'deployList'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onClickSpider (row) {
|
||||
this.$router.push(`/spiders/${row.spider_id}`)
|
||||
},
|
||||
onClickNode (row) {
|
||||
this.$router.push(`/nodes/${row.node_id}`)
|
||||
},
|
||||
onRefresh () {
|
||||
this.$store.dispatch('deploy/getDeployList', this.spiderForm._id)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-table .a-tag {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
float: left;
|
||||
margin: 10px 0 3px 0;
|
||||
}
|
||||
|
||||
.small-btn {
|
||||
float: right;
|
||||
width: 24px;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
113
frontend/src/components/TextHoverEffect/Mallki 2.vue
Normal file
113
frontend/src/components/TextHoverEffect/Mallki 2.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<a :class="className" class="link--mallki" href="#">
|
||||
{{ text }}
|
||||
<span :data-letters="text"/>
|
||||
<span :data-letters="text"/>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: 'vue-element-admin'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Mallki */
|
||||
|
||||
.link--mallki {
|
||||
font-weight: 800;
|
||||
color: #4dd9d5;
|
||||
font-family: 'Dosis', sans-serif;
|
||||
-webkit-transition: color 0.5s 0.25s;
|
||||
transition: color 0.5s 0.25s;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link--mallki:hover {
|
||||
-webkit-transition: none;
|
||||
transition: none;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.link--mallki::before {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
margin: -3px 0 0 0;
|
||||
background: #3888fa;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
-webkit-transform: translate3d(-100%, 0, 0);
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
-webkit-transition: -webkit-transform 0.4s;
|
||||
transition: transform 0.4s;
|
||||
-webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
}
|
||||
|
||||
.link--mallki:hover::before {
|
||||
-webkit-transform: translate3d(100%, 0, 0);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
|
||||
.link--mallki span {
|
||||
position: absolute;
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.link--mallki span::before {
|
||||
content: attr(data-letters);
|
||||
color: red;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
color: #3888fa;
|
||||
-webkit-transition: -webkit-transform 0.5s;
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
.link--mallki span:nth-child(2) {
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.link--mallki span:first-child::before {
|
||||
top: 0;
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
.link--mallki span:nth-child(2)::before {
|
||||
bottom: 0;
|
||||
-webkit-transform: translate3d(0, -100%, 0);
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
|
||||
.link--mallki:hover span::before {
|
||||
-webkit-transition-delay: 0.3s;
|
||||
transition-delay: 0.3s;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
-webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
|
||||
transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
|
||||
}
|
||||
</style>
|
||||
148
frontend/src/components/ThemePicker/index 2.vue
Normal file
148
frontend/src/components/ThemePicker/index 2.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const version = require('element-ui/package.json').version // element-ui version from node_modules
|
||||
const ORIGINAL_THEME = '#409EFF' // default color
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
chalk: '', // content of theme-chalk css
|
||||
theme: ORIGINAL_THEME
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
theme(val) {
|
||||
const oldVal = this.theme
|
||||
if (typeof val !== 'string') return
|
||||
const themeCluster = this.getThemeCluster(val.replace('#', ''))
|
||||
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
|
||||
console.log(themeCluster, originalCluster)
|
||||
const getHandler = (variable, id) => {
|
||||
return () => {
|
||||
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
|
||||
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
|
||||
|
||||
let styleTag = document.getElementById(id)
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style')
|
||||
styleTag.setAttribute('id', id)
|
||||
document.head.appendChild(styleTag)
|
||||
}
|
||||
styleTag.innerText = newStyle
|
||||
}
|
||||
}
|
||||
|
||||
const chalkHandler = getHandler('chalk', 'chalk-style')
|
||||
|
||||
if (!this.chalk) {
|
||||
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
|
||||
this.getCSSString(url, chalkHandler, 'chalk')
|
||||
} else {
|
||||
chalkHandler()
|
||||
}
|
||||
|
||||
const styles = [].slice.call(document.querySelectorAll('style'))
|
||||
.filter(style => {
|
||||
const text = style.innerText
|
||||
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
|
||||
})
|
||||
styles.forEach(style => {
|
||||
const { innerText } = style
|
||||
if (typeof innerText !== 'string') return
|
||||
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
|
||||
})
|
||||
this.$message({
|
||||
message: '换肤成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateStyle(style, oldCluster, newCluster) {
|
||||
let newStyle = style
|
||||
oldCluster.forEach((color, index) => {
|
||||
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
|
||||
})
|
||||
return newStyle
|
||||
},
|
||||
|
||||
getCSSString(url, callback, variable) {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
|
||||
callback()
|
||||
}
|
||||
}
|
||||
xhr.open('GET', url)
|
||||
xhr.send()
|
||||
},
|
||||
|
||||
getThemeCluster(theme) {
|
||||
const tintColor = (color, tint) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
if (tint === 0) { // when primary color is in its rgb space
|
||||
return [red, green, blue].join(',')
|
||||
} else {
|
||||
red += Math.round(tint * (255 - red))
|
||||
green += Math.round(tint * (255 - green))
|
||||
blue += Math.round(tint * (255 - blue))
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
blue = blue.toString(16)
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
}
|
||||
|
||||
const shadeColor = (color, shade) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
red = Math.round((1 - shade) * red)
|
||||
green = Math.round((1 - shade) * green)
|
||||
blue = Math.round((1 - shade) * blue)
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
blue = blue.toString(16)
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
|
||||
const clusters = [theme]
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
|
||||
}
|
||||
clusters.push(shadeColor(theme, 0.1))
|
||||
return clusters
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.theme-picker .el-color-picker__trigger {
|
||||
margin-top: 12px;
|
||||
height: 26px!important;
|
||||
width: 26px!important;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.theme-picker-dropdown .el-color-dropdown__link-btn {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
103
frontend/src/components/Tinymce/components/editorImage 2.vue
Normal file
103
frontend/src/components/Tinymce/components/editorImage 2.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">上传图片
|
||||
</el-button>
|
||||
<el-dialog :visible.sync="dialogVisible">
|
||||
<el-upload
|
||||
:multiple="true"
|
||||
:file-list="fileList"
|
||||
:show-file-list="true"
|
||||
:on-remove="handleRemove"
|
||||
:on-success="handleSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
class="editor-slide-upload"
|
||||
action="https://httpbin.org/post"
|
||||
list-type="picture-card">
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { getToken } from 'api/qiniu'
|
||||
|
||||
export default {
|
||||
name: 'EditorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#1890ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
listObj: {},
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkAllSuccess() {
|
||||
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
|
||||
},
|
||||
handleSubmit() {
|
||||
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
|
||||
if (!this.checkAllSuccess()) {
|
||||
this.$message('请等待所有图片上传成功 或 出现了网络问题,请刷新页面重新上传!')
|
||||
return
|
||||
}
|
||||
this.$emit('successCBK', arr)
|
||||
this.listObj = {}
|
||||
this.fileList = []
|
||||
this.dialogVisible = false
|
||||
},
|
||||
handleSuccess(response, file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
this.listObj[objKeyArr[i]].url = response.files.file
|
||||
this.listObj[objKeyArr[i]].hasSuccess = true
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
handleRemove(file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
delete this.listObj[objKeyArr[i]]
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const _self = this
|
||||
const _URL = window.URL || window.webkitURL
|
||||
const fileName = file.uid
|
||||
this.listObj[fileName] = {}
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.src = _URL.createObjectURL(file)
|
||||
img.onload = function() {
|
||||
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.editor-slide-upload {
|
||||
margin-bottom: 20px;
|
||||
/deep/ .el-upload--picture-card {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
frontend/src/components/TreeTable/eval 2.js
Normal file
29
frontend/src/components/TreeTable/eval 2.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @Author: jianglei
|
||||
* @Date: 2017-10-12 12:06:49
|
||||
*/
|
||||
'use strict'
|
||||
import Vue from 'vue'
|
||||
export default function treeToArray(data, expandAll, parent = null, level = null) {
|
||||
let tmp = []
|
||||
Array.from(data).forEach(function(record) {
|
||||
if (record._expanded === undefined) {
|
||||
Vue.set(record, '_expanded', expandAll)
|
||||
}
|
||||
let _level = 1
|
||||
if (level !== undefined && level !== null) {
|
||||
_level = level + 1
|
||||
}
|
||||
Vue.set(record, '_level', _level)
|
||||
// 如果有父元素
|
||||
if (parent) {
|
||||
Vue.set(record, 'parent', parent)
|
||||
}
|
||||
tmp.push(record)
|
||||
if (record.children && record.children.length > 0) {
|
||||
const children = treeToArray(record.children, expandAll, record, _level)
|
||||
tmp = tmp.concat(children)
|
||||
}
|
||||
})
|
||||
return tmp
|
||||
}
|
||||
132
frontend/src/components/Upload/singleImage 2.vue
Normal file
132
frontend/src/components/Upload/singleImage 2.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="upload-container">
|
||||
<el-upload
|
||||
:data="dataObj"
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
:on-success="handleImageSuccess"
|
||||
class="image-uploader"
|
||||
drag
|
||||
action="https://httpbin.org/post">
|
||||
<i class="el-icon-upload"/>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div class="image-preview">
|
||||
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
|
||||
<img :src="imageUrl+'?imageView2/1/w/200/h/200'">
|
||||
<div class="image-preview-action">
|
||||
<i class="el-icon-delete" @click="rmImage"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 预览效果见付费文章
|
||||
import { getToken } from '@/api/qiniu'
|
||||
|
||||
export default {
|
||||
name: 'SingleImageUpload',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tempUrl: '',
|
||||
dataObj: { token: '', key: '' }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rmImage() {
|
||||
this.emitInput('')
|
||||
},
|
||||
emitInput(val) {
|
||||
this.$emit('input', val)
|
||||
},
|
||||
handleImageSuccess() {
|
||||
this.emitInput(this.tempUrl)
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key
|
||||
const token = response.data.qiniu_token
|
||||
_self._data.dataObj.token = token
|
||||
_self._data.dataObj.key = key
|
||||
this.tempUrl = response.data.qiniu_url
|
||||
resolve(true)
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
reject(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "src/styles/mixin.scss";
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
@include clearfix;
|
||||
.image-uploader {
|
||||
width: 60%;
|
||||
float: left;
|
||||
}
|
||||
.image-preview {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
border: 1px dashed #d9d9d9;
|
||||
float: left;
|
||||
margin-left: 50px;
|
||||
.image-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.image-preview-action {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
transition: opacity .3s;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
.el-icon-delete {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.image-preview-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
136
frontend/src/components/UploadExcel/index 2.vue
Normal file
136
frontend/src/components/UploadExcel/index 2.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
|
||||
<div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
|
||||
Drop excel file here or
|
||||
<el-button :loading="loading" style="margin-left:16px;" size="mini" type="primary" @click="handleUpload">Browse</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import XLSX from 'xlsx'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
beforeUpload: Function, // eslint-disable-line
|
||||
onSuccess: Function// eslint-disable-line
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
excelData: {
|
||||
header: null,
|
||||
results: null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateData({ header, results }) {
|
||||
this.excelData.header = header
|
||||
this.excelData.results = results
|
||||
this.onSuccess && this.onSuccess(this.excelData)
|
||||
},
|
||||
handleDrop(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (this.loading) return
|
||||
const files = e.dataTransfer.files
|
||||
if (files.length !== 1) {
|
||||
this.$message.error('Only support uploading one file!')
|
||||
return
|
||||
}
|
||||
const rawFile = files[0] // only use files[0]
|
||||
|
||||
if (!this.isExcel(rawFile)) {
|
||||
this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
|
||||
return false
|
||||
}
|
||||
this.upload(rawFile)
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
},
|
||||
handleDragover(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
},
|
||||
handleUpload() {
|
||||
this.$refs['excel-upload-input'].click()
|
||||
},
|
||||
handleClick(e) {
|
||||
const files = e.target.files
|
||||
const rawFile = files[0] // only use files[0]
|
||||
if (!rawFile) return
|
||||
this.upload(rawFile)
|
||||
},
|
||||
upload(rawFile) {
|
||||
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
|
||||
|
||||
if (!this.beforeUpload) {
|
||||
this.readerData(rawFile)
|
||||
return
|
||||
}
|
||||
const before = this.beforeUpload(rawFile)
|
||||
if (before) {
|
||||
this.readerData(rawFile)
|
||||
}
|
||||
},
|
||||
readerData(rawFile) {
|
||||
this.loading = true
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = e => {
|
||||
const data = e.target.result
|
||||
const workbook = XLSX.read(data, { type: 'array' })
|
||||
const firstSheetName = workbook.SheetNames[0]
|
||||
const worksheet = workbook.Sheets[firstSheetName]
|
||||
const header = this.getHeaderRow(worksheet)
|
||||
const results = XLSX.utils.sheet_to_json(worksheet)
|
||||
this.generateData({ header, results })
|
||||
this.loading = false
|
||||
resolve()
|
||||
}
|
||||
reader.readAsArrayBuffer(rawFile)
|
||||
})
|
||||
},
|
||||
getHeaderRow(sheet) {
|
||||
const headers = []
|
||||
const range = XLSX.utils.decode_range(sheet['!ref'])
|
||||
let C
|
||||
const R = range.s.r
|
||||
/* start in the first row */
|
||||
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
|
||||
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
|
||||
/* find the cell in the first row */
|
||||
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
|
||||
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
|
||||
headers.push(hdr)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
isExcel(file) {
|
||||
return /\.(xlsx|xls|csv)$/.test(file.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.excel-upload-input{
|
||||
display: none;
|
||||
z-index: -9999;
|
||||
}
|
||||
.drop{
|
||||
border: 2px dashed #bbb;
|
||||
width: 600px;
|
||||
height: 160px;
|
||||
line-height: 160px;
|
||||
margin: 0 auto;
|
||||
font-size: 24px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
1
frontend/src/i18n/en 2.js
Normal file
1
frontend/src/i18n/en 2.js
Normal file
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
9
frontend/src/icons/index 2.js
Normal file
9
frontend/src/icons/index 2.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import Vue from 'vue'
|
||||
import SvgIcon from '@/components/SvgIcon' // svg组件
|
||||
|
||||
// register globally
|
||||
Vue.component('svg-icon', SvgIcon)
|
||||
|
||||
const requireAll = requireContext => requireContext.keys().map(requireContext)
|
||||
const req = require.context('./svg', false, /\.svg$/)
|
||||
requireAll(req)
|
||||
1
frontend/src/icons/svg/example 2.svg
Normal file
1
frontend/src/icons/svg/example 2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
|
||||
|
After Width: | Height: | Size: 497 B |
205
frontend/src/router/index 2.js
Normal file
205
frontend/src/router/index 2.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
|
||||
/* Layout */
|
||||
import Layout from '../views/layout/Layout'
|
||||
|
||||
// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
|
||||
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
/**
|
||||
* hidden: true if `hidden:true` will not show in the sidebar(default is false)
|
||||
* alwaysShow: true if set true, will always show the root menu, whatever its child routes length
|
||||
* if not set alwaysShow, only more than one route under the children
|
||||
* it will becomes nested mode, otherwise not show the root menu
|
||||
* redirect: noredirect if `redirect:noredirect` will no redirect in the breadcrumb
|
||||
* name:'router-name' the name is used by <keep-alive> (must set!!!)
|
||||
* meta : {
|
||||
title: 'title' the name show in submenu and breadcrumb (recommend set)
|
||||
icon: 'svg-name' the icon show in the sidebar
|
||||
breadcrumb: false if false, the item will hidden in breadcrumb(default is true)
|
||||
}
|
||||
**/
|
||||
export const constantRouterMap = [
|
||||
{ path: '/login', component: () => import('../views/login/index'), hidden: true },
|
||||
{ path: '/404', component: () => import('../views/404'), hidden: true },
|
||||
{ path: '/', redirect: '/home' },
|
||||
|
||||
// Crawlab Pages
|
||||
{
|
||||
path: '/home',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('../views/home/Home'),
|
||||
meta: {
|
||||
title: 'Home',
|
||||
icon: 'fa fa-home'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Node',
|
||||
path: '/nodes',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Node',
|
||||
icon: 'fa fa-server'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'NodeList',
|
||||
component: () => import('../views/node/NodeList'),
|
||||
meta: {
|
||||
title: 'Nodes',
|
||||
icon: 'fa fa-server'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'NodeDetail',
|
||||
component: () => import('../views/node/NodeDetail'),
|
||||
meta: {
|
||||
title: 'Node Detail',
|
||||
icon: 'fa fa-circle-o'
|
||||
},
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Spider',
|
||||
path: '/spiders',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Spider',
|
||||
icon: 'fa fa-bug'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'SpiderList',
|
||||
component: () => import('../views/spider/SpiderList'),
|
||||
meta: {
|
||||
title: 'Spiders',
|
||||
icon: 'fa fa-bug'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'SpiderDetail',
|
||||
component: () => import('../views/spider/SpiderDetail'),
|
||||
meta: {
|
||||
title: 'Spider Detail',
|
||||
icon: 'fa fa-circle-o'
|
||||
},
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Task',
|
||||
path: '/tasks',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Task',
|
||||
icon: 'fa fa-list'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'TaskList',
|
||||
component: () => import('../views/task/TaskList'),
|
||||
meta: {
|
||||
title: 'Tasks',
|
||||
icon: 'fa fa-list'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'TaskDetail',
|
||||
component: () => import('../views/task/TaskDetail'),
|
||||
meta: {
|
||||
title: 'Task Detail',
|
||||
icon: 'fa fa-circle-o'
|
||||
},
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Schedule',
|
||||
path: '/schedules',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Schedules',
|
||||
icon: 'fa fa-calendar'
|
||||
},
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'ScheduleList',
|
||||
component: () => import('../views/schedule/ScheduleList'),
|
||||
meta: {
|
||||
title: 'Schedules',
|
||||
icon: 'fa fa-calendar'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Deploy',
|
||||
path: '/deploys',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Deploy',
|
||||
icon: 'fa fa-cloud'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'DeployList',
|
||||
component: () => import('../views/deploy/DeployList'),
|
||||
meta: {
|
||||
title: 'Deploys',
|
||||
icon: 'fa fa-cloud'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'DeployDetail',
|
||||
component: () => import('../views/deploy/DeployDetail'),
|
||||
meta: {
|
||||
title: 'Deploy Detail',
|
||||
icon: 'fa fa-circle-o'
|
||||
},
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{ path: '*', redirect: '/404', hidden: true }
|
||||
]
|
||||
|
||||
const router = new Router({
|
||||
// mode: 'history', //后端支持可开
|
||||
scrollBehavior: () => ({ y: 0 }),
|
||||
routes: constantRouterMap
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta && to.meta.title) {
|
||||
window.document.title = `Crawlab - ${to.meta.title}`
|
||||
} else {
|
||||
window.document.title = 'Crawlab'
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
||||
9
frontend/src/store/getters 2.js
Normal file
9
frontend/src/store/getters 2.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
device: state => state.app.device,
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
name: state => state.user.name,
|
||||
roles: state => state.user.roles
|
||||
}
|
||||
export default getters
|
||||
43
frontend/src/store/modules/app 2.js
Normal file
43
frontend/src/store/modules/app 2.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const app = {
|
||||
state: {
|
||||
sidebar: {
|
||||
opened: !+Cookies.get('sidebarStatus'),
|
||||
withoutAnimation: false
|
||||
},
|
||||
device: 'desktop'
|
||||
},
|
||||
mutations: {
|
||||
TOGGLE_SIDEBAR: state => {
|
||||
if (state.sidebar.opened) {
|
||||
Cookies.set('sidebarStatus', 1)
|
||||
} else {
|
||||
Cookies.set('sidebarStatus', 0)
|
||||
}
|
||||
state.sidebar.opened = !state.sidebar.opened
|
||||
state.sidebar.withoutAnimation = false
|
||||
},
|
||||
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
||||
Cookies.set('sidebarStatus', 1)
|
||||
state.sidebar.opened = false
|
||||
state.sidebar.withoutAnimation = withoutAnimation
|
||||
},
|
||||
TOGGLE_DEVICE: (state, device) => {
|
||||
state.device = device
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
ToggleSideBar: ({ commit }) => {
|
||||
commit('TOGGLE_SIDEBAR')
|
||||
},
|
||||
CloseSideBar ({ commit }, { withoutAnimation }) {
|
||||
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||
},
|
||||
ToggleDevice ({ commit }, device) {
|
||||
commit('TOGGLE_DEVICE', device)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default app
|
||||
30
frontend/src/styles/element-ui 2.scss
Normal file
30
frontend/src/styles/element-ui 2.scss
Normal file
@@ -0,0 +1,30 @@
|
||||
//to reset element-ui default css
|
||||
.el-upload {
|
||||
input[type="file"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-upload__input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
|
||||
.el-dialog {
|
||||
transform: none;
|
||||
left: 0;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
//element ui upload
|
||||
.upload-container {
|
||||
.el-upload {
|
||||
width: 100%;
|
||||
|
||||
.el-upload-dragger {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
frontend/src/utils/auth 2.js
Normal file
15
frontend/src/utils/auth 2.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
|
||||
export function getToken () {
|
||||
return Cookies.get(TokenKey)
|
||||
}
|
||||
|
||||
export function setToken (token) {
|
||||
return Cookies.set(TokenKey, token)
|
||||
}
|
||||
|
||||
export function removeToken () {
|
||||
return Cookies.remove(TokenKey)
|
||||
}
|
||||
228
frontend/src/views/404 2.vue
Normal file
228
frontend/src/views/404 2.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<div class="wscn-http404-container">
|
||||
<div class="wscn-http404">
|
||||
<div class="pic-404">
|
||||
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
|
||||
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
</div>
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__oops">OOPS!</div>
|
||||
<div class="bullshit__info">版权所有
|
||||
<a class="link-type" href="https://wallstreetcn.com" target="_blank">华尔街见闻</a>
|
||||
</div>
|
||||
<div class="bullshit__headline">{{ message }}</div>
|
||||
<div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div>
|
||||
<a href="" class="bullshit__return-home">返回首页</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Page404',
|
||||
computed: {
|
||||
message () {
|
||||
return '网管说这个页面你不能进......'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.wscn-http404-container{
|
||||
transform: translate(-50%,-50%);
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
}
|
||||
.wscn-http404 {
|
||||
position: relative;
|
||||
width: 1200px;
|
||||
padding: 0 50px;
|
||||
overflow: hidden;
|
||||
.pic-404 {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 600px;
|
||||
overflow: hidden;
|
||||
&__parent {
|
||||
width: 100%;
|
||||
}
|
||||
&__child {
|
||||
position: absolute;
|
||||
&.left {
|
||||
width: 80px;
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
animation-name: cloudLeft;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
&.mid {
|
||||
width: 46px;
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
animation-name: cloudMid;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
&.right {
|
||||
width: 62px;
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
animation-name: cloudRight;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bullshit {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 300px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
&__oops {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: #1482f0;
|
||||
opacity: 0;
|
||||
margin-bottom: 20px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__headline {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
opacity: 0;
|
||||
margin-bottom: 10px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__info {
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: grey;
|
||||
opacity: 0;
|
||||
margin-bottom: 30px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
background: #1482f0;
|
||||
border-radius: 100px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
opacity: 0;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(60px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
32
frontend/src/views/dashboard/index 2.vue
Normal file
32
frontend/src/views/dashboard/index 2.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<div class="dashboard-text">name:{{ name }}</div>
|
||||
<div class="dashboard-text">roles:<span v-for="role in roles" :key="role">{{ role }}</span></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'name',
|
||||
'roles'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.dashboard {
|
||||
&-container {
|
||||
margin: 30px;
|
||||
}
|
||||
&-text {
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
frontend/src/views/deploy/DeployDetail 2.vue
Normal file
15
frontend/src/views/deploy/DeployDetail 2.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="">
|
||||
NodeDetail
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NodeDetail'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
84
frontend/src/views/form/index 2.vue
Normal file
84
frontend/src/views/form/index 2.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form ref="form" :model="form" label-width="120px">
|
||||
<el-form-item label="Activity name">
|
||||
<el-input v-model="form.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Activity zone">
|
||||
<el-select v-model="form.region" placeholder="please select your zone">
|
||||
<el-option label="Zone one" value="shanghai"/>
|
||||
<el-option label="Zone two" value="beijing"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Activity time">
|
||||
<el-col :span="11">
|
||||
<el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%;"/>
|
||||
</el-col>
|
||||
<el-col :span="2" class="line">-</el-col>
|
||||
<el-col :span="11">
|
||||
<el-time-picker v-model="form.date2" type="fixed-time" placeholder="Pick a time" style="width: 100%;"/>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item label="Instant delivery">
|
||||
<el-switch v-model="form.delivery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Activity type">
|
||||
<el-checkbox-group v-model="form.type">
|
||||
<el-checkbox label="Online activities" name="type"/>
|
||||
<el-checkbox label="Promotion activities" name="type"/>
|
||||
<el-checkbox label="Offline activities" name="type"/>
|
||||
<el-checkbox label="Simple brand exposure" name="type"/>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="Resources">
|
||||
<el-radio-group v-model="form.resource">
|
||||
<el-radio label="Sponsor"/>
|
||||
<el-radio label="Venue"/>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="Activity form">
|
||||
<el-input v-model="form.desc" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">Create</el-button>
|
||||
<el-button @click="onCancel">Cancel</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
name: '',
|
||||
region: '',
|
||||
date1: '',
|
||||
date2: '',
|
||||
delivery: false,
|
||||
type: [],
|
||||
resource: '',
|
||||
desc: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit () {
|
||||
this.$message('submit!')
|
||||
},
|
||||
onCancel () {
|
||||
this.$message({
|
||||
message: 'cancel!',
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.line{
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
160
frontend/src/views/home/Home 2.vue
Normal file
160
frontend/src/views/home/Home 2.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row>
|
||||
<ul class="metric-list">
|
||||
<li class="metric-item" v-for="m in metrics" @click="onClickMetric(m)" :key="m.name">
|
||||
<el-card class="metric-card" shadow="hover">
|
||||
<el-col :span="6" class="icon-col">
|
||||
<i :class="m.icon" :style="{color:m.color}"></i>
|
||||
</el-col>
|
||||
<el-col :span="18" class="text-col">
|
||||
<el-row>
|
||||
<label class="label">{{$t(m.label)}}</label>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="value">{{overviewStats[m.name]}}</div>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-card>
|
||||
</li>
|
||||
</ul>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-card shadow="hover">
|
||||
<h4 class="title">{{$t('Daily New Tasks')}}</h4>
|
||||
<div id="echarts-daily-tasks" class="echarts-box"></div>
|
||||
</el-card>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from '../../api/request'
|
||||
import echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
data () {
|
||||
return {
|
||||
echarts: {},
|
||||
overviewStats: {},
|
||||
dailyTasks: [],
|
||||
metrics: [
|
||||
{ name: 'task_count', label: 'Total Tasks', icon: 'fa fa-play', color: '#f56c6c', path: 'tasks' },
|
||||
{ name: 'spider_count', label: 'Spiders', icon: 'fa fa-bug', color: '#67c23a', path: 'spiders' },
|
||||
{ name: 'node_count', label: 'Active Nodes', icon: 'fa fa-server', color: '#409EFF', path: 'nodes' },
|
||||
{ name: 'deploy_count', label: 'Total Deploys', icon: 'fa fa-cloud', color: '#409EFF', path: 'deploys' }
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initEchartsDailyTasks () {
|
||||
const option = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: this.dailyTasks.map(d => d.date)
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
data: this.dailyTasks.map(d => d.count),
|
||||
type: 'line',
|
||||
areaStyle: {},
|
||||
smooth: true
|
||||
}],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
show: true
|
||||
}
|
||||
}
|
||||
this.echarts.dailyTasks = echarts.init(this.$el.querySelector('#echarts-daily-tasks'))
|
||||
this.echarts.dailyTasks.setOption(option)
|
||||
},
|
||||
onClickMetric (m) {
|
||||
this.$router.push(`/${m.path}`)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
request.get('/stats/get_home_stats')
|
||||
.then(response => {
|
||||
// overview stats
|
||||
this.overviewStats = response.data.overview_stats
|
||||
|
||||
// daily tasks
|
||||
this.dailyTasks = response.data.daily_tasks
|
||||
this.initEchartsDailyTasks()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.metric-list {
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
|
||||
.metric-item:last-child .metric-card {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.metric-item {
|
||||
flex-basis: 25%;
|
||||
|
||||
.metric-card:hover {
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
margin-right: 30px;
|
||||
cursor: pointer;
|
||||
|
||||
.icon-col {
|
||||
text-align: right;
|
||||
|
||||
i {
|
||||
margin-bottom: 15px;
|
||||
font-size: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-col {
|
||||
padding-left: 20px;
|
||||
height: 76px;
|
||||
text-align: center;
|
||||
|
||||
.label {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
height: 24px;
|
||||
color: grey;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#echarts-daily-tasks {
|
||||
height: 360px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
/*border: 1px solid lightgrey;*/
|
||||
}
|
||||
</style>
|
||||
79
frontend/src/views/layout/Layout 2.vue
Normal file
79
frontend/src/views/layout/Layout 2.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper">
|
||||
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"></div>
|
||||
<sidebar class="sidebar-container"/>
|
||||
<div class="main-container">
|
||||
<navbar/>
|
||||
<!--<tags-view/>-->
|
||||
<app-main/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Navbar,
|
||||
Sidebar,
|
||||
AppMain
|
||||
// TagsView
|
||||
} from './components'
|
||||
import ResizeMixin from './mixin/ResizeHandler'
|
||||
|
||||
export default {
|
||||
name: 'Layout',
|
||||
components: {
|
||||
Navbar,
|
||||
Sidebar,
|
||||
// TagsView,
|
||||
AppMain
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
computed: {
|
||||
sidebar () {
|
||||
return this.$store.state.app.sidebar
|
||||
},
|
||||
device () {
|
||||
return this.$store.state.app.device
|
||||
},
|
||||
classObj () {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
openSidebar: this.sidebar.opened,
|
||||
withoutAnimation: this.sidebar.withoutAnimation,
|
||||
mobile: this.device === 'mobile'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickOutside () {
|
||||
this.$store.dispatch('CloseSideBar', { withoutAnimation: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "../../../src/styles/mixin.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
29
frontend/src/views/layout/components/AppMain 2.vue
Normal file
29
frontend/src/views/layout/components/AppMain 2.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<section class="app-main">
|
||||
<transition name="fade-transform" mode="out-in">
|
||||
<!-- or name="fade" -->
|
||||
<router-view :key="key"></router-view>
|
||||
<!--<router-view/>-->
|
||||
</transition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AppMain',
|
||||
computed: {
|
||||
key () {
|
||||
return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-main {
|
||||
/*50 = navbar */
|
||||
min-height: calc(100vh - 50px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
34
frontend/src/views/layout/components/Sidebar/Item 2.vue
Normal file
34
frontend/src/views/layout/components/Sidebar/Item 2.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'MenuItem',
|
||||
functional: true,
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
render (h, context) {
|
||||
const { icon, title } = context.props
|
||||
const vnodes = []
|
||||
|
||||
if (icon) {
|
||||
// vnodes.push(<svg-icon icon-class={icon}/>)
|
||||
const style = {
|
||||
'margin-right': '5px',
|
||||
'z-index': 999
|
||||
}
|
||||
vnodes.push(<span class={icon} style={style}/>)
|
||||
}
|
||||
|
||||
if (title) {
|
||||
vnodes.push(<span class="title" slot='title'>{(title)}</span>)
|
||||
}
|
||||
return vnodes
|
||||
}
|
||||
}
|
||||
</script>
|
||||
41
frontend/src/views/layout/mixin/ResizeHandler 2.js
Normal file
41
frontend/src/views/layout/mixin/ResizeHandler 2.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import store from '@/store'
|
||||
|
||||
const { body } = document
|
||||
const WIDTH = 1024
|
||||
const RATIO = 3
|
||||
|
||||
export default {
|
||||
watch: {
|
||||
$route (route) {
|
||||
if (this.device === 'mobile' && this.sidebar.opened) {
|
||||
store.dispatch('CloseSideBar', { withoutAnimation: false })
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeMount () {
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
},
|
||||
mounted () {
|
||||
const isMobile = this.isMobile()
|
||||
if (isMobile) {
|
||||
store.dispatch('ToggleDevice', 'mobile')
|
||||
store.dispatch('CloseSideBar', { withoutAnimation: true })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isMobile () {
|
||||
const rect = body.getBoundingClientRect()
|
||||
return rect.width - RATIO < WIDTH
|
||||
},
|
||||
resizeHandler () {
|
||||
if (!document.hidden) {
|
||||
const isMobile = this.isMobile()
|
||||
store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop')
|
||||
|
||||
if (isMobile) {
|
||||
store.dispatch('CloseSideBar', { withoutAnimation: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
209
frontend/src/views/login/index 2.vue
Normal file
209
frontend/src/views/login/index 2.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on"
|
||||
label-position="left">
|
||||
<h3 class="title">Crawlab</h3>
|
||||
<el-form-item prop="username">
|
||||
<span class="svg-container">
|
||||
<svg-icon icon-class="user"/>
|
||||
</span>
|
||||
<el-input v-model="loginForm.username" name="username" type="text" auto-complete="on"
|
||||
placeholder="username"/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<span class="svg-container">
|
||||
<svg-icon icon-class="password"/>
|
||||
</span>
|
||||
<el-input
|
||||
:type="pwdType"
|
||||
v-model="loginForm.password"
|
||||
name="password"
|
||||
auto-complete="on"
|
||||
placeholder="password"
|
||||
@keyup.enter.native="handleLogin"/>
|
||||
<span class="show-pwd" @click="showPwd">
|
||||
<svg-icon :icon-class="pwdType === 'password' ? 'eye' : 'eye-open'"/>
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :loading="loading" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
|
||||
Sign in
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<div class="tips">
|
||||
<span style="margin-right:20px;">username: admin</span>
|
||||
<span> password: admin</span>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isvalidUsername } from '@/utils/validate'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data () {
|
||||
const validateUsername = (rule, value, callback) => {
|
||||
if (!isvalidUsername(value)) {
|
||||
callback(new Error('请输入正确的用户名'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validatePass = (rule, value, callback) => {
|
||||
if (value.length < 5) {
|
||||
callback(new Error('密码不能小于5位'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
loginForm: {
|
||||
username: 'admin',
|
||||
password: 'admin'
|
||||
},
|
||||
loginRules: {
|
||||
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
|
||||
password: [{ required: true, trigger: 'blur', validator: validatePass }]
|
||||
},
|
||||
loading: false,
|
||||
pwdType: 'password',
|
||||
redirect: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// $route: {
|
||||
// handler: function (route) {
|
||||
// this.redirect = route.query && route.query.redirect
|
||||
// },
|
||||
// immediate: true
|
||||
// }
|
||||
},
|
||||
methods: {
|
||||
showPwd () {
|
||||
if (this.pwdType === 'password') {
|
||||
this.pwdType = ''
|
||||
} else {
|
||||
this.pwdType = 'password'
|
||||
}
|
||||
},
|
||||
handleLogin () {
|
||||
this.$router.push('/')
|
||||
|
||||
// this.$refs.loginForm.validate(valid => {
|
||||
// if (valid) {
|
||||
// this.loading = true
|
||||
// this.$store.dispatch('Login', this.loginForm).then(() => {
|
||||
// this.loading = false
|
||||
// this.$router.push({ path: this.redirect || '/' })
|
||||
// }).catch(() => {
|
||||
// this.loading = false
|
||||
// })
|
||||
// } else {
|
||||
// console.log('error submit!!')
|
||||
// return false
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss">
|
||||
$bg: #2d3a4b;
|
||||
$light_gray: #eee;
|
||||
|
||||
/* reset element-ui css */
|
||||
.login-container {
|
||||
.el-input {
|
||||
display: inline-block;
|
||||
height: 47px;
|
||||
width: 85%;
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
border: 0px;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0px;
|
||||
padding: 12px 5px 12px 15px;
|
||||
color: $light_gray;
|
||||
height: 47px;
|
||||
|
||||
&:-webkit-autofill {
|
||||
-webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
color: #454545;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
$bg: #2d3a4b;
|
||||
$dark_gray: #889aa4;
|
||||
$light_gray: #eee;
|
||||
.login-container {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: $bg;
|
||||
|
||||
.login-form {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 520px;
|
||||
max-width: 100%;
|
||||
padding: 35px 35px 15px 35px;
|
||||
margin: 120px auto;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-bottom: 10px;
|
||||
|
||||
span {
|
||||
&:first-of-type {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.svg-container {
|
||||
padding: 6px 5px 6px 15px;
|
||||
color: $dark_gray;
|
||||
vertical-align: middle;
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
font-weight: 400;
|
||||
color: $light_gray;
|
||||
margin: 0px auto 40px auto;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.show-pwd {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 7px;
|
||||
font-size: 16px;
|
||||
color: $dark_gray;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
7
frontend/src/views/nested/menu1/index 2.vue
Normal file
7
frontend/src/views/nested/menu1/index 2.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template >
|
||||
<div style="padding:30px;">
|
||||
<el-alert :closable="false" title="menu 1">
|
||||
<router-view />
|
||||
</el-alert>
|
||||
</div>
|
||||
</template>
|
||||
7
frontend/src/views/nested/menu1/menu1-1/index 2.vue
Normal file
7
frontend/src/views/nested/menu1/menu1-1/index 2.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template >
|
||||
<div style="padding:30px;">
|
||||
<el-alert :closable="false" title="menu 1-1" type="success">
|
||||
<router-view />
|
||||
</el-alert>
|
||||
</div>
|
||||
</template>
|
||||
7
frontend/src/views/nested/menu1/menu1-2/index 2.vue
Normal file
7
frontend/src/views/nested/menu1/menu1-2/index 2.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div style="padding:30px;">
|
||||
<el-alert :closable="false" title="menu 1-2" type="success">
|
||||
<router-view />
|
||||
</el-alert>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template functional>
|
||||
<div style="padding:30px;">
|
||||
<el-alert :closable="false" title="menu 1-2-1" type="warning" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template functional>
|
||||
<div style="padding:30px;">
|
||||
<el-alert :closable="false" title="menu 1-2-2" type="warning" />
|
||||
</div>
|
||||
</template>
|
||||
5
frontend/src/views/nested/menu1/menu1-3/index 2.vue
Normal file
5
frontend/src/views/nested/menu1/menu1-3/index 2.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template functional>
|
||||
<div style="padding:30px;">
|
||||
<el-alert :closable="false" title="menu 1-3" type="success" />
|
||||
</div>
|
||||
</template>
|
||||
5
frontend/src/views/nested/menu2/index 2.vue
Normal file
5
frontend/src/views/nested/menu2/index 2.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div style="padding:30px;">
|
||||
<el-alert :closable="false" title="menu 2" />
|
||||
</div>
|
||||
</template>
|
||||
98
frontend/src/views/node/NodeDetail 2.vue
Normal file
98
frontend/src/views/node/NodeDetail 2.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!--selector-->
|
||||
<div class="selector">
|
||||
<label class="label">{{$t('Node')}}: </label>
|
||||
<el-select v-model="nodeForm._id" @change="onNodeChange">
|
||||
<el-option v-for="op in nodeList" :key="op._id" :value="op._id" :label="op.name"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!--tabs-->
|
||||
<el-tabs v-model="activeTabName" @tab-click="onTabClick" type="card">
|
||||
<el-tab-pane :label="$t('Overview')" name="overview">
|
||||
<node-overview></node-overview>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('Deployed Spiders')" name="spiders" v-if="false">
|
||||
{{$t('Deployed Spiders')}}
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import NodeOverview from '../../components/Overview/NodeOverview'
|
||||
|
||||
export default {
|
||||
name: 'NodeDetail',
|
||||
components: {
|
||||
NodeOverview
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
activeTabName: 'overview'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeList',
|
||||
'nodeForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onTabClick () {
|
||||
},
|
||||
onNodeChange (id) {
|
||||
this.$router.push(`/nodes/${id}`)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// get list of nodes
|
||||
this.$store.dispatch('node/getNodeList')
|
||||
|
||||
// get node basic info
|
||||
this.$store.dispatch('node/getNodeData', this.$route.params.id)
|
||||
|
||||
// get node deploy list
|
||||
this.$store.dispatch('node/getDeployList', this.$route.params.id)
|
||||
|
||||
// get node task list
|
||||
this.$store.dispatch('node/getTaskList', this.$route.params.id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
margin-top: -7px;
|
||||
/*float: right;*/
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.selector .el-select {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.selector {
|
||||
.el-select {
|
||||
.el-input {
|
||||
.el-input_inner {
|
||||
height: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
frontend/src/views/result/ResultDetail 2.vue
Normal file
95
frontend/src/views/result/ResultDetail 2.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!--selector-->
|
||||
<div class="selector">
|
||||
<label class="label">Spider: </label>
|
||||
<el-select v-model="spiderForm._id" @change="onSpiderChange">
|
||||
<el-option v-for="op in spiderList" :key="op._id" :value="op._id" :label="op.name"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!--tabs-->
|
||||
<el-tabs v-model="activeTabName" @tab-click="onTabClick" type="card">
|
||||
<el-tab-pane label="Overview" name="overview">
|
||||
<spider-overview/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Files" name="files">
|
||||
<file-list/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import FileList from '../../components/FileList/FileList'
|
||||
import SpiderOverview from '../../components/Overview/SpiderOverview'
|
||||
|
||||
export default {
|
||||
name: 'NodeDetail',
|
||||
components: {
|
||||
FileList,
|
||||
SpiderOverview
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
activeTabName: 'overview'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderList',
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('file', [
|
||||
'currentPath'
|
||||
]),
|
||||
...mapState('deploy', [
|
||||
'deployList'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onTabClick () {
|
||||
},
|
||||
onSpiderChange (id) {
|
||||
this.$router.push(`/spiders/${id}`)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// get the list of the spiders
|
||||
this.$store.dispatch('spider/getSpiderList')
|
||||
|
||||
// get spider basic info
|
||||
this.$store.dispatch('spider/getSpiderData', this.$route.params.id)
|
||||
.then(() => {
|
||||
// get spider file info
|
||||
this.$store.dispatch('file/getFileList', this.spiderForm.src)
|
||||
})
|
||||
|
||||
// get spider deploys
|
||||
this.$store.dispatch('spider/getDeployList', this.$route.params.id)
|
||||
|
||||
// get spider tasks
|
||||
this.$store.dispatch('spider/getTaskList', this.$route.params.id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
/*float: right;*/
|
||||
z-index: 999;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.selector .el-select {
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
15
frontend/src/views/schedule/ScheduleList 2.vue
Normal file
15
frontend/src/views/schedule/ScheduleList 2.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
Schedule List
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ScheduleList'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
99
frontend/src/views/spider/SpiderDetail 2.vue
Normal file
99
frontend/src/views/spider/SpiderDetail 2.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!--selector-->
|
||||
<div class="selector">
|
||||
<label class="label">{{$t('Spider')}}: </label>
|
||||
<el-select v-model="spiderForm._id" @change="onSpiderChange">
|
||||
<el-option v-for="op in spiderList" :key="op._id" :value="op._id" :label="op.name"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!--tabs-->
|
||||
<el-tabs v-model="activeTabName" @tab-click="onTabClick" type="card">
|
||||
<el-tab-pane :label="$t('Overview')" name="overview">
|
||||
<spider-overview/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('Files')" name="files">
|
||||
<file-list/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import FileList from '../../components/FileList/FileList'
|
||||
import SpiderOverview from '../../components/Overview/SpiderOverview'
|
||||
|
||||
export default {
|
||||
name: 'NodeDetail',
|
||||
components: {
|
||||
FileList,
|
||||
SpiderOverview
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
activeTabName: 'overview'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderList',
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('file', [
|
||||
'currentPath'
|
||||
]),
|
||||
...mapState('deploy', [
|
||||
'deployList'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onTabClick () {
|
||||
},
|
||||
onSpiderChange (id) {
|
||||
this.$router.push(`/spiders/${id}`)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// get the list of the spiders
|
||||
this.$store.dispatch('spider/getSpiderList')
|
||||
|
||||
// get spider basic info
|
||||
this.$store.dispatch('spider/getSpiderData', this.$route.params.id)
|
||||
.then(() => {
|
||||
// get spider file info
|
||||
this.$store.dispatch('file/getFileList', this.spiderForm.src)
|
||||
})
|
||||
|
||||
// get spider deploys
|
||||
this.$store.dispatch('spider/getDeployList', this.$route.params.id)
|
||||
|
||||
// get spider tasks
|
||||
this.$store.dispatch('spider/getTaskList', this.$route.params.id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
/*float: right;*/
|
||||
z-index: 999;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.selector .el-select {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.label {
|
||||
text-align: right;
|
||||
width: 80px;
|
||||
}
|
||||
</style>
|
||||
78
frontend/src/views/table/index 2.vue
Normal file
78
frontend/src/views/table/index 2.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-table
|
||||
v-loading="listLoading"
|
||||
:data="list"
|
||||
element-loading-text="Loading"
|
||||
border
|
||||
fit
|
||||
highlight-current-row>
|
||||
<el-table-column align="center" label="ID" width="95">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.$index }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Title">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.title }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Author" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.author }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Pageviews" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.pageviews }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column class-name="status-col" label="Status" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="created_at" label="Display_time" width="200">
|
||||
<template slot-scope="scope">
|
||||
<i class="el-icon-time"/>
|
||||
<span>{{ scope.row.display_time }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList } from '@/api/table'
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
statusFilter (status) {
|
||||
const statusMap = {
|
||||
published: 'success',
|
||||
draft: 'gray',
|
||||
deleted: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
list: null,
|
||||
listLoading: true
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.listLoading = true
|
||||
getList(this.listQuery).then(response => {
|
||||
this.list = response.data.items
|
||||
this.listLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
104
frontend/src/views/task/TaskDetail 2.vue
Normal file
104
frontend/src/views/task/TaskDetail 2.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!--tabs-->
|
||||
<el-tabs v-model="activeTabName" @tab-click="onTabClick" type="card">
|
||||
<el-tab-pane :label="$t('Overview')" name="overview">
|
||||
<task-overview/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('Log')" name="log">
|
||||
<div class="log-view">
|
||||
<pre>
|
||||
{{taskLog}}
|
||||
</pre>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('Results')" name="results">
|
||||
<general-table-view :data="taskResultsData"
|
||||
:columns="taskResultsColumns"
|
||||
:page-num="resultsPageNum"
|
||||
:page-size="resultsPageSize"
|
||||
:total="taskResultsTotalCount"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import TaskOverview from '../../components/Overview/TaskOverview'
|
||||
import GeneralTableView from '../../components/TableView/GeneralTableView'
|
||||
|
||||
export default {
|
||||
name: 'TaskDetail',
|
||||
components: {
|
||||
GeneralTableView,
|
||||
TaskOverview
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
activeTabName: 'overview'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('task', [
|
||||
'taskLog',
|
||||
'taskResultsData',
|
||||
'taskResultsColumns',
|
||||
'taskResultsTotalCount'
|
||||
]),
|
||||
...mapState('file', [
|
||||
'currentPath'
|
||||
]),
|
||||
...mapState('deploy', [
|
||||
'deployList'
|
||||
]),
|
||||
resultsPageNum: {
|
||||
get () {
|
||||
return this.$store.state.task.resultsPageNum
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_RESULTS_PAGE_NUM', value)
|
||||
}
|
||||
},
|
||||
resultsPageSize: {
|
||||
get () {
|
||||
return this.$store.state.task.resultsPageSize
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_RESULTS_PAGE_SIZE', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onTabClick () {
|
||||
},
|
||||
onSpiderChange (id) {
|
||||
this.$router.push(`/spiders/${id}`)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('task/getTaskData', this.$route.params.id)
|
||||
this.$store.dispatch('task/getTaskLog', this.$route.params.id)
|
||||
this.$store.dispatch('task/getTaskResults', this.$route.params.id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
/*float: right;*/
|
||||
z-index: 999;
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.selector .el-select {
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user