mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-14 11:15:59 +08:00
refactor: Add new frontend as submodule
This commit is contained in:
parent
bc58419bb1
commit
ffd219abfe
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "web"]
|
||||||
|
path = web
|
||||||
|
url = https://github.com/sub-store-org/Sub-Store-Front-End.git
|
6
backend/dist/cron-sync-artifacts.min.js
vendored
6
backend/dist/cron-sync-artifacts.min.js
vendored
File diff suppressed because one or more lines are too long
6
backend/dist/sub-store-parser.loon.min.js
vendored
6
backend/dist/sub-store-parser.loon.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.6.4",
|
"version": "2.7.0",
|
||||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -27,36 +27,39 @@ function ConditionalFilter({ rule }) {
|
|||||||
return {
|
return {
|
||||||
name: 'Conditional Filter',
|
name: 'Conditional Filter',
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
return proxies.map(proxy => isMatch(rule, proxy));
|
return proxies.map((proxy) => isMatch(rule, proxy));
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMatch(rule, proxy) {
|
function isMatch(rule, proxy) {
|
||||||
// leaf node
|
// leaf node
|
||||||
if (!rule.operator) {
|
if (!rule.operator) {
|
||||||
switch (rule.proposition) {
|
switch (rule.proposition) {
|
||||||
case "IN":
|
case 'IN':
|
||||||
return rule.value.indexOf(proxy[rule.attr]) !== -1;
|
return rule.value.indexOf(proxy[rule.attr]) !== -1;
|
||||||
case "CONTAINS":
|
case 'CONTAINS':
|
||||||
if (typeof proxy[rule.attr] !== "string") return false;
|
if (typeof proxy[rule.attr] !== 'string') return false;
|
||||||
return proxy[rule.attr].indexOf(rule.value) !== -1;
|
return proxy[rule.attr].indexOf(rule.value) !== -1;
|
||||||
case "EQUALS":
|
case 'EQUALS':
|
||||||
return proxy[rule.attr] === rule.value;
|
return proxy[rule.attr] === rule.value;
|
||||||
case "EXISTS":
|
case 'EXISTS':
|
||||||
return proxy[rule.attr] !== null || typeof proxy[rule.attr] !== "undefined";
|
return (
|
||||||
|
proxy[rule.attr] !== null ||
|
||||||
|
typeof proxy[rule.attr] !== 'undefined'
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown proposition: ${rule.proposition}`);
|
throw new Error(`Unknown proposition: ${rule.proposition}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// operator nodes
|
// operator nodes
|
||||||
switch (rule.operator) {
|
switch (rule.operator) {
|
||||||
case "AND":
|
case 'AND':
|
||||||
return rule.child.every(child => isMatch(child, proxy));
|
return rule.child.every((child) => isMatch(child, proxy));
|
||||||
case "OR":
|
case 'OR':
|
||||||
return rule.child.some(child => isMatch(child, proxy));
|
return rule.child.some((child) => isMatch(child, proxy));
|
||||||
case "NOT":
|
case 'NOT':
|
||||||
return !isMatch(rule.child, proxy);
|
return !isMatch(rule.child, proxy);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown operator: ${rule.operator}`);
|
throw new Error(`Unknown operator: ${rule.operator}`);
|
||||||
|
6
backend/sub-store.min.js
vendored
6
backend/sub-store.min.js
vendored
File diff suppressed because one or more lines are too long
1
web
Submodule
1
web
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit b10b708c3420a1b4cdc71a7f1b845de701a2382b
|
25
web/.gitignore
vendored
25
web/.gitignore
vendored
@ -1,25 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/dist
|
|
||||||
yarn.lock
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|
||||||
.vercel
|
|
@ -1,3 +0,0 @@
|
|||||||
# Ignore artifacts:
|
|
||||||
build
|
|
||||||
coverage
|
|
@ -1,47 +0,0 @@
|
|||||||
printWidth : 80 # 显示宽度
|
|
||||||
tabWidth : 2 # tab 宽度
|
|
||||||
useTabs : false # 使用 tab 而不是空格
|
|
||||||
semi : false # 使用分号
|
|
||||||
singleQuote : true # 使用单引号
|
|
||||||
jsxSingleQuote : false # 在 JSX 中使用单引号而不是双引号
|
|
||||||
bracketSpacing : true # 在对象花括号内打印空格 true { foo: bar } false {foo: bar}
|
|
||||||
arrowParens : "avoid" # 箭头函数只有一个参数的时候的周围的括号 "always" - (x) => x "avoid" - x => x
|
|
||||||
embeddedLanguageFormatting : "auto" # "auto" - 嵌入代码如果 Prettier 可以识别则格式化它 "off" - 永远不要自动格式化
|
|
||||||
bracketSameLine : false # 多行属性的 HTML(HTML、JSX、Vue、Angular)标签的 ">" 放在最后一行的末尾,而不是单独在下一行(不适用于自闭合元素)
|
|
||||||
#htmlWhitespaceSensitivity : "strict"
|
|
||||||
vueIndentScriptAndStyle : true # 在 Vue 文件中缩进 <script> 和 <style> 标签
|
|
||||||
insertPragma : false # 是否插入一个特殊 @format 标记指定文件已使用 Prettier 格式化
|
|
||||||
|
|
||||||
# 行尾风格
|
|
||||||
# "lf" – 仅换行 ( \n),常见于 Linux 和 macOS 以及 git repos 内部
|
|
||||||
# "crlf" - 回车 + 换行字符 ( \r\n),常见于 Windows
|
|
||||||
# "cr" - 仅回车字符 ( \r),很少使用
|
|
||||||
# "auto" - 保持现有的行尾(一个文件中的混合值通过查看第一行之后使用的内容进行标准化)
|
|
||||||
endOfLine : "lf"
|
|
||||||
|
|
||||||
# 需要提供注释才允许格式化
|
|
||||||
# /**
|
|
||||||
# * @prettier 或 @format
|
|
||||||
# */
|
|
||||||
requirePragma : false
|
|
||||||
|
|
||||||
# 对象属性的引号风格
|
|
||||||
# "as-needed" 仅在需要时在对象属性周围添加引号
|
|
||||||
# "consistent" 如果对象中至少一个属性需要引号,则所有属性都使用引号
|
|
||||||
# "preserve" 尊重对象属性中的引号
|
|
||||||
quoteProps : "consistent"
|
|
||||||
|
|
||||||
# 在多行逗号分隔的句法结构中尽可能打印尾随逗号
|
|
||||||
# "es5" 在 ES5 中有效的尾随逗号(对象、数组等),TypeScript 中的类型参数中没有尾随逗号
|
|
||||||
# "none" 没有尾随逗号。
|
|
||||||
# "all" 尽可能使用尾随逗号(包括函数参数和调用)。要运行,以这种方式格式化的 JavaScript 代码需要一个支持 ES2017(Node.js 8+ 或现代浏览器)或下级编译的引擎。这还可以在 TypeScript 中的类型参数中启用尾随逗号(自 2018 年 1 月发布的 TypeScript 2.7 起支持)
|
|
||||||
trailingComma : "es5"
|
|
||||||
|
|
||||||
# 例外配置覆盖
|
|
||||||
overrides :
|
|
||||||
- files :
|
|
||||||
- "*.ts"
|
|
||||||
- "*.tsx"
|
|
||||||
options :
|
|
||||||
semi : true
|
|
||||||
arrowParens : "always"
|
|
@ -1,24 +0,0 @@
|
|||||||
# web
|
|
||||||
|
|
||||||
## Project setup
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and hot-reloads for development
|
|
||||||
```
|
|
||||||
npm run serve
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and minifies for production
|
|
||||||
```
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lints and fixes files
|
|
||||||
```
|
|
||||||
npm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
### Customize configuration
|
|
||||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
12298
web/package-lock.json
generated
12298
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "web",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"serve": "vue-cli-service serve",
|
|
||||||
"build": "vue-cli-service build",
|
|
||||||
"lint": "vue-cli-service lint",
|
|
||||||
"vercel-build": "npm run build"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@dzangolab/vue-country-flag-icon": "^0.2.0",
|
|
||||||
"axios": "^0.20.0",
|
|
||||||
"chartist": "^0.11.4",
|
|
||||||
"core-js": "^3.6.5",
|
|
||||||
"material-design-icons-iconfont": "^5.0.1",
|
|
||||||
"timeago.js": "^4.0.2",
|
|
||||||
"v-clipboard": "^2.2.3",
|
|
||||||
"vee-validate": "^3.4.5",
|
|
||||||
"vue": "^2.6.12",
|
|
||||||
"vue-chartist": "^2.3.1",
|
|
||||||
"vue-i18n": "^8.22.2",
|
|
||||||
"vue-qrcode-component": "^2.1.1",
|
|
||||||
"vue-router": "^3.4.3",
|
|
||||||
"vue-world-map": "^0.1.1",
|
|
||||||
"vuetify": "^2.3.10",
|
|
||||||
"vuex": "^3.5.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@vue/cli-plugin-babel": "^4.5.6",
|
|
||||||
"@vue/cli-plugin-eslint": "^4.5.6",
|
|
||||||
"@vue/cli-service": "^4.5.6",
|
|
||||||
"babel-eslint": "^10.1.0",
|
|
||||||
"eslint": "^6.7.2",
|
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
|
||||||
"sass": "~1.26.11",
|
|
||||||
"sass-loader": "^8.0.0",
|
|
||||||
"vue-cli-plugin-vuetify": "~2.0.7",
|
|
||||||
"vue-template-compiler": "^2.6.12",
|
|
||||||
"vuetify-loader": "^1.3.0"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:vue/essential",
|
|
||||||
"eslint:recommended"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"parser": "babel-eslint"
|
|
||||||
},
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
"browserslist": [
|
|
||||||
"> 1%",
|
|
||||||
"last 2 versions",
|
|
||||||
"not dead"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Vuetify Md Pro | 404</title>
|
|
||||||
<script type="text/javascript">
|
|
||||||
// Single Page Apps for GitHub Pages
|
|
||||||
// https://github.com/rafrex/spa-github-pages
|
|
||||||
// Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// This script takes the current url and converts the path and query
|
|
||||||
// string into just a query string, and then redirects the browser
|
|
||||||
// to the new url with only a query string and hash fragment,
|
|
||||||
// e.g. http://www.foo.tld/one/two?a=b&c=d#qwe, becomes
|
|
||||||
// http://www.foo.tld/?p=/one/two&q=a=b~and~c=d#qwe
|
|
||||||
// Note: this 404.html file must be at least 512 bytes for it to work
|
|
||||||
// with Internet Explorer (it is currently > 512 bytes)
|
|
||||||
|
|
||||||
// If you're creating a Project Pages site and NOT using a custom domain,
|
|
||||||
// then set segmentCount to 1 (enterprise users may need to set it to > 1).
|
|
||||||
// This way the code will only replace the route part of the path, and not
|
|
||||||
// the real directory in which the app resides, for example:
|
|
||||||
// https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
|
|
||||||
// https://username.github.io/repo-name/?p=/one/two&q=a=b~and~c=d#qwe
|
|
||||||
// Otherwise, leave segmentCount as 0.
|
|
||||||
var segmentCount = 1;
|
|
||||||
|
|
||||||
var l = window.location;
|
|
||||||
l.replace(
|
|
||||||
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
|
|
||||||
l.pathname.split('/').slice(0, 1 + segmentCount).join('/') + '/?p=/' +
|
|
||||||
l.pathname.slice(1).split('/').slice(segmentCount).join('/').replace(/&/g, '~and~') +
|
|
||||||
(l.search ? '&q=' + l.search.slice(1).replace(/&/g, '~and~') : '') +
|
|
||||||
l.hash
|
|
||||||
);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB |
@ -1,31 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
|
||||||
<meta http-equiv="Pragma" content="no-cache" />
|
|
||||||
<meta http-equiv="Expires" content="0" />
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
|
||||||
<meta name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"/>
|
|
||||||
<link rel="Bookmark" href="https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"/>
|
|
||||||
<link rel="shortcut icon" href="https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"/>
|
|
||||||
<link rel="apple-touch-icon" href="https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"/>
|
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
|
||||||
<title>Sub-Store</title>
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
|
||||||
Please enable it to continue.</strong>
|
|
||||||
</noscript>
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
113
web/src/App.vue
113
web/src/App.vue
@ -1,113 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-app>
|
|
||||||
<TopToolbar></TopToolbar>
|
|
||||||
<v-main>
|
|
||||||
<router-view></router-view>
|
|
||||||
</v-main>
|
|
||||||
<BottomNav ref="bottomNavBar"></BottomNav>
|
|
||||||
<v-snackbar :value="successMessage" app bottom color="success" elevation="20">
|
|
||||||
{{ successMessage }}
|
|
||||||
</v-snackbar>
|
|
||||||
|
|
||||||
<v-snackbar :value="errorMessage" app bottom color="error" elevation="20">
|
|
||||||
{{ errorMessage }}
|
|
||||||
</v-snackbar>
|
|
||||||
|
|
||||||
<v-overlay :value="isLoading">
|
|
||||||
<v-progress-circular indeterminate size="64" color="primary"></v-progress-circular>
|
|
||||||
</v-overlay>
|
|
||||||
</v-app>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import TopToolbar from "@/components/TopToolbar";
|
|
||||||
import BottomNav from "@/components/BottomNav";
|
|
||||||
import { showError } from "@/utils";
|
|
||||||
|
|
||||||
|
|
||||||
async function initStore(store) {
|
|
||||||
await store.dispatch('FETCH_SUBSCRIPTIONS').catch(() => {
|
|
||||||
showError(`无法拉取订阅列表!`);
|
|
||||||
});
|
|
||||||
await store.dispatch("FETCH_COLLECTIONS").catch(() => {
|
|
||||||
showError(`无法拉取组合订阅列表!`);
|
|
||||||
});
|
|
||||||
await store.dispatch("FETCH_ARTIFACTS").catch(() => {
|
|
||||||
showError(`无法拉取配置列表!`);
|
|
||||||
});
|
|
||||||
await store.dispatch("FETCH_SETTINGS").catch(() => {
|
|
||||||
showError(`无法拉取配置列表!`);
|
|
||||||
});
|
|
||||||
await store.dispatch("FETCH_ENV").catch(() => {
|
|
||||||
showError(`无法获取当前运行环境!`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
TopToolbar,
|
|
||||||
BottomNav,
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
initStore(this.$store);
|
|
||||||
|
|
||||||
const vuetify = this.$vuetify;
|
|
||||||
|
|
||||||
if (window.matchMedia) {
|
|
||||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
||||||
vuetify.theme.dark = true;
|
|
||||||
}
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
|
||||||
console.log(`changed to ${e.matches ? "dark" : "light"} mode`)
|
|
||||||
vuetify.theme.dark = e.matches ? true : false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted (){
|
|
||||||
const bottomNavBar = this.$refs.bottomNavBar.$el;
|
|
||||||
const height = bottomNavBar.offsetHeight || bottomNavBar.clientHeight;
|
|
||||||
this.$store.commit("SET_BOTTOM_NAVBAR_HEIGHT", height);
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
successMessage() {
|
|
||||||
return this.$store.state.successMessage;
|
|
||||||
},
|
|
||||||
errorMessage() {
|
|
||||||
return this.$store.state.errorMessage;
|
|
||||||
},
|
|
||||||
isLoading() {
|
|
||||||
return this.$store.state.isLoading;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
successMessage() {
|
|
||||||
if (this.$store.state.snackbarTimer) {
|
|
||||||
clearTimeout(this.$store.state.snackbarTimer);
|
|
||||||
}
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", "");
|
|
||||||
}, 3000);
|
|
||||||
this.$store.commit("SET_SNACK_BAR_TIMER", timer);
|
|
||||||
},
|
|
||||||
errorMessage() {
|
|
||||||
if (this.$store.state.snackbarTimer) {
|
|
||||||
clearTimeout(this.$store.state.snackbarTimer);
|
|
||||||
}
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", "");
|
|
||||||
}, 3000);
|
|
||||||
this.$store.commit("SET_SNACK_BAR_TIMER", timer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import "./assets/css/app";
|
|
||||||
@import "./assets/css/general.css";
|
|
||||||
</style>
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = __webpack_public_path__ + "img/clint-mckoy.36f95307.jpg";
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,48 +0,0 @@
|
|||||||
[v-cloak] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.text-pre-wrap {
|
|
||||||
white-space: pre-wrap !important;
|
|
||||||
}
|
|
||||||
.v-navigation-drawer {
|
|
||||||
padding-top: constant(safe-area-inset-top) !important;
|
|
||||||
padding-top: env(safe-area-inset-top) !important;
|
|
||||||
}
|
|
||||||
.v-bottom-sheet.v-dialog--fullscreen {
|
|
||||||
padding-top: constant(safe-area-inset-top) !important;
|
|
||||||
padding-top: env(safe-area-inset-top) !important;
|
|
||||||
}
|
|
||||||
.v-app-bar {
|
|
||||||
height: auto !important;
|
|
||||||
padding-top: constant(safe-area-inset-top) !important;
|
|
||||||
padding-top: env(safe-area-inset-top) !important;
|
|
||||||
}
|
|
||||||
.v-toolbar {
|
|
||||||
height: auto !important;
|
|
||||||
padding-top: constant(safe-area-inset-top) !important;
|
|
||||||
padding-top: env(safe-area-inset-top) !important;
|
|
||||||
}
|
|
||||||
.v-toolbar__content {
|
|
||||||
padding-left: 12px !important;
|
|
||||||
padding-right: 12px !important;
|
|
||||||
}
|
|
||||||
.v-main {
|
|
||||||
margin-top: constant(safe-area-inset-top) !important;
|
|
||||||
margin-top: env(safe-area-inset-top) !important;
|
|
||||||
margin-bottom: constant(safe-area-inset-bottom) !important;
|
|
||||||
margin-bottom: env(safe-area-inset-bottom) !important;
|
|
||||||
}
|
|
||||||
.v-main .container {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.v-bottom-navigation,
|
|
||||||
.v-bottom-sheet {
|
|
||||||
padding-bottom: constant(safe-area-inset-bottom);
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
.v-bottom-navigation {
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
.v-bottom-navigation button {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = __webpack_public_path__ + "img/lock.9ae20e99.jpg";
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = __webpack_public_path__ + "img/login.d6d3bb09.jpg";
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = __webpack_public_path__ + "img/logo.82b9c7a5.png";
|
|
@ -1 +0,0 @@
|
|||||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>
|
|
Before Width: | Height: | Size: 539 B |
@ -1 +0,0 @@
|
|||||||
module.exports = __webpack_public_path__ + "img/pricing.f76b550f.jpg";
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = __webpack_public_path__ + "img/register.85b37874.jpg";
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = __webpack_public_path__ + "img/vuetify.31b0d032.svg";
|
|
@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-bottom-navigation
|
|
||||||
v-model="activeItem"
|
|
||||||
app
|
|
||||||
color="primary"
|
|
||||||
fixed
|
|
||||||
grow
|
|
||||||
>
|
|
||||||
<v-btn :to="{path: '/'}" value="subscription">
|
|
||||||
<span>订阅</span>
|
|
||||||
<v-icon>flight_takeoff</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<v-btn :to="{path: '/cloud'}" value="artifacts">
|
|
||||||
<span>同步</span>
|
|
||||||
<v-icon>mdi-cloud</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<v-btn :to="{path: '/user'}" value="user">
|
|
||||||
<span>我的</span>
|
|
||||||
<v-icon>mdi-account</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-bottom-navigation>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data: () => {
|
|
||||||
return {
|
|
||||||
activeItem: 'subscription'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>flag</v-icon>
|
|
||||||
国旗
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
国旗
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
添加或者删除节点国旗。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
工作模式
|
|
||||||
<v-radio-group v-model="mode">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="添加" value="ADD"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="删除" value="REMOVE"/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ["args"],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
mode: "ADD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (typeof this.args !== 'undefined')
|
|
||||||
this.mode = this.args === true ? "ADD" : "REMOVE";
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
mode() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: this.mode === "ADD"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,64 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class = "float-menu-switch-wrapper" ref = "floatMenuSwitch">
|
|
||||||
<v-speed-dial v-model = "fab" :direction = "direction" :transition = "transition"
|
|
||||||
>
|
|
||||||
<template v-slot:activator>
|
|
||||||
<v-btn v-model = "fab" color = "primary" fab>
|
|
||||||
<v-icon v-if = "fab"> mdi-close</v-icon>
|
|
||||||
<v-icon v-else> mdi-gesture-double-tap</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<slot></slot>
|
|
||||||
</v-speed-dial>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name : "FloatMenu",
|
|
||||||
data (){
|
|
||||||
return {
|
|
||||||
direction : "top",
|
|
||||||
fab : false,
|
|
||||||
fling : false,
|
|
||||||
hover : false,
|
|
||||||
tabs : null,
|
|
||||||
transition : "scale-transition",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
updated (){
|
|
||||||
const floatMenuSwitch = this.$refs.floatMenuSwitch;
|
|
||||||
floatMenuSwitch.style.bottom = 2 * this.bottomNavBarHeight + "px";
|
|
||||||
},
|
|
||||||
computed : {
|
|
||||||
bottomNavBarHeight (){
|
|
||||||
return this.$store.state.bottomNavBarHeight;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang = "scss" scoped>
|
|
||||||
.float-menu-switch-wrapper {
|
|
||||||
position : fixed;
|
|
||||||
right : 16px;;
|
|
||||||
z-index : 99;
|
|
||||||
|
|
||||||
.v-speed-dial > button.v-btn.v-btn--round {
|
|
||||||
margin-right : 0;
|
|
||||||
width : 40px;
|
|
||||||
height : 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .v-speed-dial__list button.theme--light.v-btn {
|
|
||||||
margin-right : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is for documentation purposes and will not be needed in your application */
|
|
||||||
#create .v-speed-dial {
|
|
||||||
position : absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
#create .v-btn--floating {
|
|
||||||
position : relative;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,120 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>compress</v-icon>
|
|
||||||
节点去重
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{ on }">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">节点去重</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
删除或者重命名重复节点。提供以下两种选项:<br/>
|
|
||||||
- 删除:删除多余重复节点。<br/>
|
|
||||||
- 重命名:对重复节点添加序号进行重命名。可以定制序号显示的格式
|
|
||||||
(用空格分割的数字),序号位置 (前缀或者后缀),连接符。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
操作
|
|
||||||
<v-radio-group v-model="action">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="重命名" value="rename"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="删除" value="delete"/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
|
|
||||||
<v-form v-if="action === 'rename'">
|
|
||||||
序号位置
|
|
||||||
<v-radio-group v-model="position" row>
|
|
||||||
<v-radio label="前缀" value="front"/>
|
|
||||||
<v-radio label="后缀" value="back"/>
|
|
||||||
</v-radio-group>
|
|
||||||
序号格式
|
|
||||||
<v-text-field
|
|
||||||
v-model="template"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
hint="例如:𝟘 𝟙 𝟚 𝟛 𝟜 𝟝 𝟞 𝟟 𝟠 𝟡"
|
|
||||||
placeholder="序号显示格式,用空格分隔"
|
|
||||||
/>
|
|
||||||
连接符
|
|
||||||
<v-text-field
|
|
||||||
v-model="link"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
hint="例如:-"
|
|
||||||
placeholder="节点名和序号的连接符"
|
|
||||||
/>
|
|
||||||
</v-form>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ["args"],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
action: "rename",
|
|
||||||
position: "back",
|
|
||||||
template: "0 1 2 3 4 5 6 7 8 9",
|
|
||||||
link: "-",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
attr() {
|
|
||||||
return `${this.action}/${this.position}/${this.template}${this.link}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
save() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: {
|
|
||||||
action: this.action,
|
|
||||||
position: this.position,
|
|
||||||
template: this.template,
|
|
||||||
link: this.link,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
attr() {
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (typeof this.args !== 'undefined') {
|
|
||||||
this.action = this.args.action || this.action;
|
|
||||||
this.position = this.args.position || this.position;
|
|
||||||
this.template = this.args.template || this.template;
|
|
||||||
this.link = typeof this.args.link !== 'undefined' ? this.args.link : this.link;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
@ -1,167 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item v-for="(proxy, idx) in proxies" :key="idx">
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title class="wrap-text" v-text="proxy.name"></v-list-item-title>
|
|
||||||
<v-chip-group>
|
|
||||||
<v-chip color="primary" outlined x-small>
|
|
||||||
<v-icon left x-small>mdi-server</v-icon>
|
|
||||||
{{ proxy.type.toUpperCase() }}
|
|
||||||
</v-chip>
|
|
||||||
<v-chip v-if="proxy.udp" color="blue" outlined x-small>
|
|
||||||
<v-icon left x-small>mdi-fire</v-icon>
|
|
||||||
UDP
|
|
||||||
</v-chip>
|
|
||||||
<v-chip v-if="proxy.tfo" color="success" outlined x-small>
|
|
||||||
<v-icon left x-small>mdi-flash</v-icon>
|
|
||||||
TFO
|
|
||||||
</v-chip>
|
|
||||||
<v-chip v-if="proxy['skip-cert-verify']" color="error" outlined x-small>
|
|
||||||
<v-icon left x-small>error</v-icon>
|
|
||||||
SCERT
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-list-item-action>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-btn
|
|
||||||
v-if="proxy.type !== 'http'"
|
|
||||||
icon
|
|
||||||
@click="showQRCode(idx)"
|
|
||||||
>
|
|
||||||
<v-icon color="grey lighten-1" small>mdi-qrcode</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-btn icon @click="showInfo(idx)">
|
|
||||||
<v-icon color="grey lighten-1" small>mdi-information</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-list-item-action>
|
|
||||||
</v-list-item>
|
|
||||||
<v-dialog
|
|
||||||
v-model="dialog"
|
|
||||||
>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>
|
|
||||||
{{ info.name }}
|
|
||||||
</v-card-title>
|
|
||||||
|
|
||||||
<v-card-text>
|
|
||||||
<h4>{{ info.isp }}</h4>
|
|
||||||
<h4>{{ info.region }}</h4>
|
|
||||||
<h4>{{ info.ip }}</h4>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
<v-dialog
|
|
||||||
v-model="showQR"
|
|
||||||
>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>
|
|
||||||
{{ info.name }}
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
@click="copyLink()"
|
|
||||||
>
|
|
||||||
<v-icon>content_copy</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<vue-q-r-code-component
|
|
||||||
:text="qr"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-list>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {axios} from "@/utils";
|
|
||||||
import VueQRCodeComponent from 'vue-qrcode-component';
|
|
||||||
|
|
||||||
const flags = new Map([["AC", "🇦🇨"], ["AF", "🇦🇫"], ["AI", "🇦🇮"], ["AL", "🇦🇱"], ["AM", "🇦🇲"], ["AQ", "🇦🇶"], ["AR", "🇦🇷"], ["AS", "🇦🇸"], ["AT", "🇦🇹"], ["AU", "🇦🇺"], ["AW", "🇦🇼"], ["AX", "🇦🇽"], ["AZ", "🇦🇿"], ["BB", "🇧🇧"], ["BD", "🇧🇩"], ["BE", "🇧🇪"], ["BF", "🇧🇫"], ["BG", "🇧🇬"], ["BH", "🇧🇭"], ["BI", "🇧🇮"], ["BJ", "🇧🇯"], ["BM", "🇧🇲"], ["BN", "🇧🇳"], ["BO", "🇧🇴"], ["BR", "🇧🇷"], ["BS", "🇧🇸"], ["BT", "🇧🇹"], ["BV", "🇧🇻"], ["BW", "🇧🇼"], ["BY", "🇧🇾"], ["BZ", "🇧🇿"], ["CA", "🇨🇦"], ["CF", "🇨🇫"], ["CH", "🇨🇭"], ["CK", "🇨🇰"], ["CL", "🇨🇱"], ["CM", "🇨🇲"], ["CN", "🇨🇳"], ["CO", "🇨🇴"], ["CP", "🇨🇵"], ["CR", "🇨🇷"], ["CU", "🇨🇺"], ["CV", "🇨🇻"], ["CW", "🇨🇼"], ["CX", "🇨🇽"], ["CY", "🇨🇾"], ["CZ", "🇨🇿"], ["DE", "🇩🇪"], ["DG", "🇩🇬"], ["DJ", "🇩🇯"], ["DK", "🇩🇰"], ["DM", "🇩🇲"], ["DO", "🇩🇴"], ["DZ", "🇩🇿"], ["EA", "🇪🇦"], ["EC", "🇪🇨"], ["EE", "🇪🇪"], ["EG", "🇪🇬"], ["EH", "🇪🇭"], ["ER", "🇪🇷"], ["ES", "🇪🇸"], ["ET", "🇪🇹"], ["EU", "🇪🇺"], ["FI", "🇫🇮"], ["FJ", "🇫🇯"], ["FK", "🇫🇰"], ["FM", "🇫🇲"], ["FO", "🇫🇴"], ["FR", "🇫🇷"], ["GA", "🇬🇦"], ["GB", "🇬🇧"], ["HK", "🇭🇰"], ["HU", "🇭🇺"], ["ID", "🇮🇩"], ["IE", "🇮🇪"], ["IL", "🇮🇱"], ["IM", "🇮🇲"], ["IN", "🇮🇳"], ["IS", "🇮🇸"], ["IT", "🇮🇹"], ["JP", "🇯🇵"], ["KR", "🇰🇷"], ["LU", "🇱🇺"], ["MO", "🇲🇴"], ["MX", "🇲🇽"], ["MY", "🇲🇾"], ["NL", "🇳🇱"], ["PH", "🇵🇭"], ["RO", "🇷🇴"], ["RS", "🇷🇸"], ["RU", "🇷🇺"], ["RW", "🇷🇼"], ["SA", "🇸🇦"], ["SB", "🇸🇧"], ["SC", "🇸🇨"], ["SD", "🇸🇩"], ["SE", "🇸🇪"], ["SG", "🇸🇬"], ["TH", "🇹🇭"], ["TN", "🇹🇳"], ["TO", "🇹🇴"], ["TR", "🇹🇷"], ["TV", "🇹🇻"], ["TW", "🇨🇳"], ["UK", "🇬🇧"], ["UM", "🇺🇲"], ["US", "🇺🇸"], ["UY", "🇺🇾"], ["UZ", "🇺🇿"], ["VA", "🇻🇦"], ["VE", "🇻🇪"], ["VG", "🇻🇬"], ["VI", "🇻🇮"], ["VN", "🇻🇳"], ["ZA", "🇿🇦"]])
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "ProxyList",
|
|
||||||
props: ['url', 'sub', 'raw'],
|
|
||||||
components: {VueQRCodeComponent},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
proxies: [],
|
|
||||||
uris: [],
|
|
||||||
dialog: false,
|
|
||||||
showQR: false,
|
|
||||||
info: {
|
|
||||||
name: "",
|
|
||||||
isp: "",
|
|
||||||
region: "",
|
|
||||||
ip: ""
|
|
||||||
},
|
|
||||||
qr: "",
|
|
||||||
link: ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetch() {
|
|
||||||
try {
|
|
||||||
await axios.get(this.raw ? `${this.url}?raw=true` : this.url).then(resp => {
|
|
||||||
let {data} = resp;
|
|
||||||
// eslint-disable-next-line no-debugger
|
|
||||||
this.proxies = data;
|
|
||||||
})
|
|
||||||
|
|
||||||
await axios.get(this.raw ? `${this.url}?target=URI&raw=true` : `${this.url}?target=URI`).then(resp => {
|
|
||||||
const {data} = resp;
|
|
||||||
this.uris = data.split("\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
// fix http offset
|
|
||||||
this.proxies.forEach((p, idx) => {
|
|
||||||
if (p.type === 'http') {
|
|
||||||
this.uris.splice(idx, 0, null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async showInfo(idx) {
|
|
||||||
const {server, name} = this.proxies[idx];
|
|
||||||
const res = await axios.get(`/utils/IP_API/${encodeURIComponent(server)}`).then(resp => resp.data);
|
|
||||||
this.info.name = name;
|
|
||||||
this.info.isp = `ISP:${res.isp}`;
|
|
||||||
this.info.region = `地区:${flags.get(res.countryCode)} ${res.regionName} ${res.city}`;
|
|
||||||
this.info.ip = `IP:${res.query}`
|
|
||||||
this.dialog = true
|
|
||||||
},
|
|
||||||
|
|
||||||
copyLink() {
|
|
||||||
this.$clipboard(this.link);
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", `节点链接已复制到剪贴板!`);
|
|
||||||
},
|
|
||||||
|
|
||||||
showQRCode(idx) {
|
|
||||||
this.qr = this.uris[idx];
|
|
||||||
this.link = this.uris[idx];
|
|
||||||
this.info.name = this.proxies[idx].name;
|
|
||||||
this.showQR = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fetch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.wrap-text {
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,114 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>code</v-icon>
|
|
||||||
正则删除
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
正则删除
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
根据正则表达式删除节点名中的字段,注意正则表达式需要注意转义。
|
|
||||||
<br/>这里是一个合法的正则表达式:
|
|
||||||
<br/>
|
|
||||||
<b>IEPL|IPLC</b>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
正则表达式
|
|
||||||
<v-chip-group
|
|
||||||
column
|
|
||||||
>
|
|
||||||
<v-chip
|
|
||||||
v-for="(regex, idx) in regexps"
|
|
||||||
:key="idx"
|
|
||||||
close
|
|
||||||
close-icon="mdi-delete"
|
|
||||||
@click="edit(idx)"
|
|
||||||
@click:close="remove(idx)"
|
|
||||||
>
|
|
||||||
{{ regex }}
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.regex"
|
|
||||||
append-icon="mdi-send"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
placeholder="添加新正则表达式"
|
|
||||||
solo
|
|
||||||
@click:append="add(form.regex)"
|
|
||||||
@keyup.enter="add(form.regex)"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ['args'],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
mode: "IN",
|
|
||||||
form: {
|
|
||||||
regex: ""
|
|
||||||
},
|
|
||||||
regexps: [],
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
add(keyword) {
|
|
||||||
if (keyword) {
|
|
||||||
this.regexps.push(keyword);
|
|
||||||
this.form.regex = "";
|
|
||||||
} else {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", "正则表达式不能为空!");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edit(idx) {
|
|
||||||
this.form.regex = this.regexps[idx];
|
|
||||||
this.remove(idx);
|
|
||||||
},
|
|
||||||
remove(idx) {
|
|
||||||
this.regexps.splice(idx, 1);
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: this.regexps
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
regexps() {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.regexps = this.args || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,136 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>filter_list</v-icon>
|
|
||||||
正则过滤
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
正则过滤
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
根据正则表达式过滤节点。如果设置为保留模式,则匹配<b>任何一个</b>正则表达式的节点会被保留,否则会被过滤。
|
|
||||||
正则表达式需要注意转义。
|
|
||||||
<br/>这里是一个合法的正则表达式:
|
|
||||||
<br/>
|
|
||||||
<b>IEPL|IPLC</b>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
工作模式
|
|
||||||
<v-radio-group v-model="mode">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="保留模式" value="IN"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="过滤模式" value="OUT"/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
正则表达式
|
|
||||||
<v-chip-group
|
|
||||||
column
|
|
||||||
>
|
|
||||||
<v-chip
|
|
||||||
v-for="(regex, idx) in regexps"
|
|
||||||
:key="idx"
|
|
||||||
close
|
|
||||||
close-icon="mdi-delete"
|
|
||||||
@click="edit(idx)"
|
|
||||||
@click:close="remove(idx)"
|
|
||||||
>
|
|
||||||
{{ regex }}
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.regex"
|
|
||||||
append-icon="mdi-send"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
placeholder="添加新正则表达式"
|
|
||||||
solo
|
|
||||||
@click:append="add(form.regex)"
|
|
||||||
@keyup.enter="add(form.regex)"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ['args'],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
mode: "IN",
|
|
||||||
form: {
|
|
||||||
regex: ""
|
|
||||||
},
|
|
||||||
regexps: [],
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
add(keyword) {
|
|
||||||
if (keyword) {
|
|
||||||
this.regexps.push(keyword);
|
|
||||||
this.form.regex = "";
|
|
||||||
} else {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", "正则表达式不能为空!");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edit(idx) {
|
|
||||||
this.form.regex = this.regexps[idx];
|
|
||||||
this.remove(idx);
|
|
||||||
},
|
|
||||||
remove(idx) {
|
|
||||||
this.regexps.splice(idx, 1);
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: {
|
|
||||||
regex: this.regexps,
|
|
||||||
keep: this.mode === 'IN'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
regexps() {
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
mode() {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.args) {
|
|
||||||
this.regexps = this.args.regex || [];
|
|
||||||
if (typeof this.args.keep !== 'undefined') this.mode = this.args.keep ? "IN" : "OUT";
|
|
||||||
else this.mode = "IN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,139 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>filter_list</v-icon>
|
|
||||||
正则重命名
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
正则重命名
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
使用替换节点名中的字段。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
正则表达式
|
|
||||||
<v-chip-group
|
|
||||||
column
|
|
||||||
>
|
|
||||||
<v-chip
|
|
||||||
v-for="(chip, idx) in chips"
|
|
||||||
:key="idx"
|
|
||||||
close
|
|
||||||
close-icon="mdi-delete"
|
|
||||||
@click="edit(idx)"
|
|
||||||
@click:close="remove(idx)"
|
|
||||||
>
|
|
||||||
{{ chip }}
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.regex"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
placeholder="正则表达式"
|
|
||||||
solo
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.replace"
|
|
||||||
append-icon="mdi-send"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
placeholder="替换为"
|
|
||||||
solo
|
|
||||||
@click:append="add"
|
|
||||||
@keyup.enter="add"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ['args'],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
mode: "IN",
|
|
||||||
form: {
|
|
||||||
regex: "",
|
|
||||||
replace: ""
|
|
||||||
},
|
|
||||||
regexps: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
chips() {
|
|
||||||
return this.regexps.map(k => {
|
|
||||||
const {expr, now} = k;
|
|
||||||
return `${expr} ⇒ ${now.length === 0 ? "∅" : now}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
add() {
|
|
||||||
if (this.form.regex) {
|
|
||||||
this.regexps.push({
|
|
||||||
expr: this.form.regex,
|
|
||||||
now: this.form.replace || ""
|
|
||||||
});
|
|
||||||
this.form.regex = "";
|
|
||||||
this.form.replace = "";
|
|
||||||
} else {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", "正则表达式不能为空!");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edit(idx) {
|
|
||||||
this.form.regex = this.regexps[idx].expr;
|
|
||||||
this.form.replace = this.regexps[idx].now;
|
|
||||||
this.remove(idx);
|
|
||||||
},
|
|
||||||
remove(idx) {
|
|
||||||
this.regexps.splice(idx, 1);
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: this.regexps
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.regexps = this.args || [];
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
regexps() {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,114 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>sort</v-icon>
|
|
||||||
正则排序
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
正则排序
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
根据给出的正则表达式的顺序对节点进行排序,无法匹配的节点将会按照正序排列。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
正则表达式
|
|
||||||
<v-chip-group
|
|
||||||
column
|
|
||||||
>
|
|
||||||
<v-chip
|
|
||||||
v-for="(item, idx) in items"
|
|
||||||
:key="idx"
|
|
||||||
close
|
|
||||||
close-icon="mdi-delete"
|
|
||||||
@click="edit(idx)"
|
|
||||||
@click:close="remove(idx)"
|
|
||||||
>
|
|
||||||
{{ item }}
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
<v-text-field
|
|
||||||
v-model="form.item"
|
|
||||||
append-icon="mdi-send"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
placeholder="添加新正则表达式"
|
|
||||||
solo
|
|
||||||
@click:append="add(form.item)"
|
|
||||||
@keyup.enter="add(form.item)"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ['args'],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
selection: null,
|
|
||||||
currentTag: null,
|
|
||||||
form: {
|
|
||||||
item: ""
|
|
||||||
},
|
|
||||||
items: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.args) {
|
|
||||||
this.items = this.args || [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
items() {
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
add(item) {
|
|
||||||
if (item) {
|
|
||||||
this.items.push(item);
|
|
||||||
this.form.item = "";
|
|
||||||
} else {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", "正则表达式不能为空!");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edit(idx) {
|
|
||||||
this.form.item = this.items[idx];
|
|
||||||
this.remove(idx);
|
|
||||||
},
|
|
||||||
remove(idx) {
|
|
||||||
this.items.splice(idx, 1);
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: this.items
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,100 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>flag</v-icon>
|
|
||||||
区域过滤
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
区域过滤器
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
根据区域过滤节点,至少需要保留一个区域!选中的区域会被保留。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<v-chip-group v-model="selection" active-class="primary accent-4" column multiple>
|
|
||||||
<v-chip
|
|
||||||
v-for="region in regions"
|
|
||||||
:key="region.name"
|
|
||||||
:value="region.value"
|
|
||||||
class="ma-2"
|
|
||||||
label
|
|
||||||
>
|
|
||||||
{{ region.name }}
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const regions = [
|
|
||||||
{
|
|
||||||
name: "🇭🇰 香港",
|
|
||||||
value: "HK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "🇨🇳 台湾",
|
|
||||||
value: "TW"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "🇸🇬 新加坡",
|
|
||||||
value: "SG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "🇯🇵 日本",
|
|
||||||
value: "JP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "🇺🇸 美国",
|
|
||||||
value: "US"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "🇬🇧 英国",
|
|
||||||
value: "UK"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
export default {
|
|
||||||
props: ["args"],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
regions,
|
|
||||||
selection: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.selection = this.args || [];
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
selection() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: this.selection
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,86 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>dns</v-icon>
|
|
||||||
节点域名解析
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
节点域名解析
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
将节点域名解析成 IP 地址
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
服务提供商
|
|
||||||
<v-radio-group v-model="provider">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="Google" value="Google"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="IP-API" value="IP-API"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="Cloudflare" value="Cloudflare"/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ["args"],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
provider: "Google"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (typeof this.args !== "undefined") {
|
|
||||||
this.provider = this.args.provider || "Google";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
save() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: {
|
|
||||||
provider: this.provider
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
provider() {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,103 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>code</v-icon>
|
|
||||||
脚本过滤器
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
脚本过滤器
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
用一段脚本过滤节点,可以提供脚本链接或者直接输入脚本。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
输入
|
|
||||||
<v-radio-group v-model="mode">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="链接" value="link"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="脚本" value="script"/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
<v-textarea
|
|
||||||
v-model="content"
|
|
||||||
:label="hint"
|
|
||||||
auto-grow
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
solo
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ["args"],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
mode: "link",
|
|
||||||
content: ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hint() {
|
|
||||||
return this.mode === 'link' ? "请输入链接地址" : "请输入一段脚本"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (typeof this.args !== 'undefined') {
|
|
||||||
this.mode = this.args.mode;
|
|
||||||
this.content = this.args.content;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
mode() {
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
content() {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
save() {
|
|
||||||
if (this.content) {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: {
|
|
||||||
mode: this.mode,
|
|
||||||
content: this.content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,103 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>code</v-icon>
|
|
||||||
脚本操作
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
脚本操作
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
用一段脚本操作节点,可以提供脚本链接或者直接输入脚本。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
输入
|
|
||||||
<v-radio-group v-model="mode">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="链接" value="link"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="脚本" value="script"/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
<v-textarea
|
|
||||||
v-model="content"
|
|
||||||
:label="hint"
|
|
||||||
auto-grow
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
solo
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ["args"],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
mode: "link",
|
|
||||||
content: ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hint() {
|
|
||||||
return this.mode === 'link' ? "请输入链接地址" : "请输入一段脚本"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (typeof this.args !== 'undefined') {
|
|
||||||
this.mode = this.args.mode;
|
|
||||||
this.content = this.args.content;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
mode() {
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
content() {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
save() {
|
|
||||||
if (this.content) {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: {
|
|
||||||
mode: this.mode,
|
|
||||||
content: this.content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,77 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>sort_by_alpha</v-icon>
|
|
||||||
节点排序
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
节点排序
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
根据节点名排序,一共有正序,逆序,随机三种模式。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
模式
|
|
||||||
<v-radio-group v-model="mode">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="正序" value="asc"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="逆序" value="desc"/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="随机" value="random"/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: ["args"],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
mode: "asc"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.mode = this.args;
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
mode() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
args: this.mode
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,46 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-fab-transition>
|
|
||||||
<v-speed-dial
|
|
||||||
v-model="opened"
|
|
||||||
absolute
|
|
||||||
bottom
|
|
||||||
direction="top"
|
|
||||||
fab
|
|
||||||
left
|
|
||||||
small
|
|
||||||
transition="slide-y-reverse-transition"
|
|
||||||
>
|
|
||||||
<template #activator>
|
|
||||||
<v-btn
|
|
||||||
fab
|
|
||||||
>
|
|
||||||
<v-icon v-if="opened">mdi-close</v-icon>
|
|
||||||
<v-icon v-else>mdi-plus</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-btn
|
|
||||||
color="primary"
|
|
||||||
fab
|
|
||||||
>
|
|
||||||
<v-icon>mdi-plus</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
color="primary"
|
|
||||||
fab
|
|
||||||
>
|
|
||||||
<v-icon>create_new_folder</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-speed-dial>
|
|
||||||
</v-fab-transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
opened: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<v-app-bar
|
|
||||||
:clipped="true"
|
|
||||||
:mini-variant="false"
|
|
||||||
app fixed
|
|
||||||
>
|
|
||||||
<v-toolbar-title><h3>{{ title }}</h3></v-toolbar-title>
|
|
||||||
</v-app-bar>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data: () => {
|
|
||||||
return {
|
|
||||||
showMenu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
title: function () {
|
|
||||||
return this.$store.state.title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,109 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="ml-1 mr-1 mb-1 mt-1">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon color="primary" left>cloud_circle</v-icon>
|
|
||||||
节点类型过滤
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="$emit('up', idx)">
|
|
||||||
<v-icon>keyboard_arrow_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('down', idx)">
|
|
||||||
<v-icon>keyboard_arrow_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="$emit('deleteProcess', idx)">
|
|
||||||
<v-icon color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog>
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>help</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline">
|
|
||||||
节点类型过滤器
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
根据节点类型过滤节点,至少需要保留一种类型!选中的类型会被保留。
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<v-chip-group v-model="selection" active-class="primary accent-4" column multiple>
|
|
||||||
<v-chip
|
|
||||||
v-for="type in types"
|
|
||||||
:key="type.name"
|
|
||||||
:value="type.value"
|
|
||||||
class="ma-2"
|
|
||||||
label
|
|
||||||
>
|
|
||||||
{{ type.name }}
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const types = [
|
|
||||||
{
|
|
||||||
name: "Shadowsocks",
|
|
||||||
value: "ss"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Shadowsocks R",
|
|
||||||
value: "ssr"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "VMess",
|
|
||||||
value: "vmess"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "VLess",
|
|
||||||
value: "vless"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Trojan",
|
|
||||||
value: "trojan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "HTTP(s)",
|
|
||||||
value: "http"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Socks5",
|
|
||||||
value: "socks5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Snell",
|
|
||||||
value: "snell"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
export default {
|
|
||||||
props: ['args'],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
idx: this.$vnode.key,
|
|
||||||
types,
|
|
||||||
selection: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.selection = this.args || [];
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
selection() {
|
|
||||||
this.$emit("dataChanged", {
|
|
||||||
idx: this.idx,
|
|
||||||
type: "Type Filter",
|
|
||||||
args: this.selection
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,9 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {VCard} from 'vuetify/lib'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Card',
|
|
||||||
|
|
||||||
extends: VCard
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,69 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-list-item
|
|
||||||
:active-class="`primary ${!isDark ? 'black' : 'white'}--text`"
|
|
||||||
:href="href"
|
|
||||||
:rel="href && href !== '#' ? 'noopener' : undefined"
|
|
||||||
:target="href && href !== '#' ? '_blank' : undefined"
|
|
||||||
:to="item.to"
|
|
||||||
>
|
|
||||||
<v-list-item-icon
|
|
||||||
v-if="text"
|
|
||||||
class="v-list-item__icon--text"
|
|
||||||
v-text="computedText"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<v-list-item-icon v-else-if="item.icon">
|
|
||||||
<v-icon v-text="item.icon"/>
|
|
||||||
</v-list-item-icon>
|
|
||||||
|
|
||||||
<v-list-item-content v-if="item.title || item.subtitle">
|
|
||||||
<v-list-item-title v-text="item.title"/>
|
|
||||||
|
|
||||||
<v-list-item-subtitle v-text="item.subtitle"/>
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Themeable from 'vuetify/lib/mixins/themeable'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Item',
|
|
||||||
|
|
||||||
mixins: [Themeable],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({
|
|
||||||
href: undefined,
|
|
||||||
icon: undefined,
|
|
||||||
subtitle: undefined,
|
|
||||||
title: undefined,
|
|
||||||
to: undefined
|
|
||||||
})
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
computedText() {
|
|
||||||
if (!this.item || !this.item.title) return ''
|
|
||||||
|
|
||||||
let text = ''
|
|
||||||
|
|
||||||
this.item.title.split(' ').forEach(val => {
|
|
||||||
text += val.substring(0, 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
return text
|
|
||||||
},
|
|
||||||
href() {
|
|
||||||
return this.item.href || (!this.item.to ? '#' : undefined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,123 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-list-group
|
|
||||||
:color="barColor !== 'rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.7)' ? 'white' : 'grey darken-1'"
|
|
||||||
:group="group"
|
|
||||||
:prepend-icon="item.icon"
|
|
||||||
:sub-group="subGroup"
|
|
||||||
append-icon="mdi-menu-down"
|
|
||||||
>
|
|
||||||
<template v-slot:activator>
|
|
||||||
<v-list-item-icon
|
|
||||||
v-if="text"
|
|
||||||
class="v-list-item__icon--text"
|
|
||||||
v-text="computedText"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<v-list-item-avatar
|
|
||||||
v-else-if="item.avatar"
|
|
||||||
class="align-self-center"
|
|
||||||
color="grey"
|
|
||||||
>
|
|
||||||
<v-img src="https://demos.creative-tim.com/material-dashboard-pro/assets/img/faces/avatar.jpg"/>
|
|
||||||
</v-list-item-avatar>
|
|
||||||
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title v-text="item.title"/>
|
|
||||||
</v-list-item-content>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-for="(child, i) in children">
|
|
||||||
<base-item-sub-group
|
|
||||||
v-if="child.children"
|
|
||||||
:key="`sub-group-${i}`"
|
|
||||||
:item="child"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<base-item
|
|
||||||
v-else
|
|
||||||
:key="`item-${i}`"
|
|
||||||
:item="child"
|
|
||||||
text
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</v-list-group>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Utilities
|
|
||||||
import kebabCase from 'lodash/kebabCase'
|
|
||||||
import {mapState} from 'vuex'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ItemGroup',
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({
|
|
||||||
avatar: undefined,
|
|
||||||
group: undefined,
|
|
||||||
title: undefined,
|
|
||||||
children: []
|
|
||||||
})
|
|
||||||
},
|
|
||||||
subGroup: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapState(['barColor']),
|
|
||||||
children() {
|
|
||||||
return this.item.children.map(item => ({
|
|
||||||
...item,
|
|
||||||
to: !item.to ? undefined : `${this.item.group}/${item.to}`
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
computedText() {
|
|
||||||
if (!this.item || !this.item.title) return ''
|
|
||||||
|
|
||||||
let text = ''
|
|
||||||
|
|
||||||
this.item.title.split(' ').forEach(val => {
|
|
||||||
text += val.substring(0, 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
return text
|
|
||||||
},
|
|
||||||
group() {
|
|
||||||
return this.genGroup(this.item.children)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
genGroup(children) {
|
|
||||||
return children
|
|
||||||
.filter(item => item.to)
|
|
||||||
.map(item => {
|
|
||||||
const parent = item.group || this.item.group
|
|
||||||
let group = `${parent}/${kebabCase(item.to)}`
|
|
||||||
|
|
||||||
if (item.children) {
|
|
||||||
group = `${group}|${this.genGroup(item.children)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return group
|
|
||||||
}).join('|')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.v-list-group__activator p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,25 +0,0 @@
|
|||||||
<template>
|
|
||||||
<base-item-group
|
|
||||||
:item="item"
|
|
||||||
sub-group
|
|
||||||
text
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ItemSubGroup',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({
|
|
||||||
avatar: undefined,
|
|
||||||
group: undefined,
|
|
||||||
title: undefined,
|
|
||||||
children: []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,64 +0,0 @@
|
|||||||
<script>
|
|
||||||
// Components
|
|
||||||
import {VAlert, VBtn, VIcon} from 'vuetify/lib'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MaterialAlert',
|
|
||||||
|
|
||||||
extends: VAlert,
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
__cachedDismissible() {
|
|
||||||
if (!this.dismissible) return null
|
|
||||||
|
|
||||||
const color = 'white'
|
|
||||||
|
|
||||||
return this.$createElement(VBtn, {
|
|
||||||
staticClass: 'v-alert__dismissible',
|
|
||||||
props: {
|
|
||||||
color,
|
|
||||||
icon: true,
|
|
||||||
small: true
|
|
||||||
},
|
|
||||||
attrs: {
|
|
||||||
'aria-label': this.$vuetify.lang.t(this.closeLabel)
|
|
||||||
},
|
|
||||||
on: {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
click: () => (this.isActive = false)
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
this.$createElement(VIcon, {
|
|
||||||
props: {color}
|
|
||||||
}, '$vuetify.icons.cancel')
|
|
||||||
])
|
|
||||||
},
|
|
||||||
classes() {
|
|
||||||
return {
|
|
||||||
...VAlert.options.computed.classes.call(this),
|
|
||||||
'v-alert--material': true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasColoredIcon() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
|
|
||||||
.v-alert--material
|
|
||||||
margin-top: 32px
|
|
||||||
|
|
||||||
.v-alert__icon
|
|
||||||
background-color: #FFFFFF
|
|
||||||
height: 44px
|
|
||||||
min-width: 44px
|
|
||||||
top: -36px
|
|
||||||
|
|
||||||
.v-alert__dismissible
|
|
||||||
align-self: flex-start
|
|
||||||
margin: 0 !important
|
|
||||||
padding: 0 !important
|
|
||||||
</style>
|
|
@ -1,168 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card
|
|
||||||
v-bind="$attrs"
|
|
||||||
:class="classes"
|
|
||||||
class="v-card--material pa-3"
|
|
||||||
>
|
|
||||||
<div class="d-flex grow flex-wrap">
|
|
||||||
<v-avatar
|
|
||||||
v-if="avatar"
|
|
||||||
class="mx-auto v-card--material__avatar elevation-12"
|
|
||||||
color="grey"
|
|
||||||
size="128"
|
|
||||||
>
|
|
||||||
<v-img :src="avatar"/>
|
|
||||||
</v-avatar>
|
|
||||||
|
|
||||||
<v-sheet
|
|
||||||
v-else
|
|
||||||
:class="{
|
|
||||||
'pa-7': !$slots.image
|
|
||||||
}"
|
|
||||||
:color="color"
|
|
||||||
:max-height="icon ? 90 : undefined"
|
|
||||||
:width="inline || icon ? 'auto' : '100%'"
|
|
||||||
class="text-start v-card--material__heading mb-n6"
|
|
||||||
dark
|
|
||||||
>
|
|
||||||
<slot
|
|
||||||
v-if="$slots.heading"
|
|
||||||
name="heading"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<slot
|
|
||||||
v-else-if="$slots.image"
|
|
||||||
name="image"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else-if="title && !icon"
|
|
||||||
class="display-1 font-weight-light"
|
|
||||||
v-text="title"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<v-icon
|
|
||||||
v-else-if="icon"
|
|
||||||
size="32"
|
|
||||||
v-text="icon"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="text"
|
|
||||||
class="headline font-weight-thin"
|
|
||||||
v-text="text"
|
|
||||||
/>
|
|
||||||
</v-sheet>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="$slots['after-heading']"
|
|
||||||
class="ml-6"
|
|
||||||
>
|
|
||||||
<slot name="after-heading"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
v-if="hoverReveal"
|
|
||||||
class="text-center py-0 mt-n12"
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<slot name="reveal-actions"/>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else-if="icon && title"
|
|
||||||
class="ml-4"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
|
|
||||||
class="card-title font-weight-light"
|
|
||||||
v-text="title"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<slot/>
|
|
||||||
|
|
||||||
<template v-if="$slots.actions">
|
|
||||||
<v-divider class="mt-2"/>
|
|
||||||
|
|
||||||
<v-card-actions class="pb-0">
|
|
||||||
<slot name="actions"/>
|
|
||||||
</v-card-actions>
|
|
||||||
</template>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'MaterialCard',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
avatar: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: 'success'
|
|
||||||
},
|
|
||||||
hoverReveal: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
inline: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
classes() {
|
|
||||||
return {
|
|
||||||
'v-card--material--has-heading': this.hasHeading,
|
|
||||||
'v-card--material--hover-reveal': this.hoverReveal
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasHeading() {
|
|
||||||
return Boolean(this.$slots.heading || this.title || this.icon)
|
|
||||||
},
|
|
||||||
hasAltHeading() {
|
|
||||||
return Boolean(this.$slots.heading || (this.title && this.icon))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.v-card--material
|
|
||||||
&__avatar
|
|
||||||
position: relative
|
|
||||||
top: -64px
|
|
||||||
margin-bottom: -32px
|
|
||||||
|
|
||||||
&__heading
|
|
||||||
position: relative
|
|
||||||
top: -40px
|
|
||||||
transition: .3s ease
|
|
||||||
z-index: 1
|
|
||||||
|
|
||||||
&.v-card--material--hover-reveal:hover
|
|
||||||
.v-card--material__heading
|
|
||||||
transform: translateY(-40px)
|
|
||||||
</style>
|
|
@ -1,95 +0,0 @@
|
|||||||
<template>
|
|
||||||
<base-material-card
|
|
||||||
v-bind="$attrs"
|
|
||||||
v-on="$listeners"
|
|
||||||
class="v-card--material-chart"
|
|
||||||
>
|
|
||||||
<template v-slot:heading>
|
|
||||||
<chartist
|
|
||||||
:data="data"
|
|
||||||
:event-handlers="eventHandlers"
|
|
||||||
:options="options"
|
|
||||||
:ratio="ratio"
|
|
||||||
:responsive-options="responsiveOptions"
|
|
||||||
:type="type"
|
|
||||||
style="max-height: 150px;"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<slot
|
|
||||||
slot="reveal-actions"
|
|
||||||
name="reveal-actions"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<slot/>
|
|
||||||
|
|
||||||
<slot
|
|
||||||
slot="actions"
|
|
||||||
name="actions"
|
|
||||||
/>
|
|
||||||
</base-material-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'MaterialChartCard',
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
data: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
eventHandlers: {
|
|
||||||
type: Array,
|
|
||||||
default: () => ([])
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
ratio: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
responsiveOptions: {
|
|
||||||
type: Array,
|
|
||||||
default: () => ([])
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
validator: v => ['Bar', 'Line', 'Pie'].includes(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.v-card--material-chart
|
|
||||||
p
|
|
||||||
color: #999
|
|
||||||
|
|
||||||
.v-card--material__heading
|
|
||||||
max-height: 185px
|
|
||||||
|
|
||||||
.ct-label
|
|
||||||
color: inherit
|
|
||||||
opacity: .7
|
|
||||||
font-size: 0.975rem
|
|
||||||
font-weight: 100
|
|
||||||
|
|
||||||
.ct-grid
|
|
||||||
stroke: rgba(255, 255, 255, 0.2)
|
|
||||||
|
|
||||||
.ct-series-a .ct-point,
|
|
||||||
.ct-series-a .ct-line,
|
|
||||||
.ct-series-a .ct-bar,
|
|
||||||
.ct-series-a .ct-slice-donut
|
|
||||||
stroke: rgba(255, 255, 255, .8)
|
|
||||||
|
|
||||||
.ct-series-a .ct-slice-pie,
|
|
||||||
.ct-series-a .ct-area
|
|
||||||
fill: rgba(255, 255, 255, .4)
|
|
||||||
</style>
|
|
@ -1,70 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-menu
|
|
||||||
v-model="value"
|
|
||||||
v-bind="$attrs"
|
|
||||||
:transition="transition"
|
|
||||||
offset-y
|
|
||||||
>
|
|
||||||
<template v-slot:activator="{ attrs, on }">
|
|
||||||
<v-btn
|
|
||||||
v-bind="attrs"
|
|
||||||
v-on="on"
|
|
||||||
:color="color"
|
|
||||||
default
|
|
||||||
min-width="200"
|
|
||||||
rounded
|
|
||||||
>
|
|
||||||
<slot/>
|
|
||||||
|
|
||||||
<v-icon>
|
|
||||||
mdi-{{ value ? 'menu-up' : 'menu-down' }}
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<v-sheet>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-item
|
|
||||||
v-for="(item, i) in items"
|
|
||||||
:key="i"
|
|
||||||
@click="$(`click:action-${item.id}`)"
|
|
||||||
>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title v-text="item.text"/>
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-sheet>
|
|
||||||
</v-menu>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Mixins
|
|
||||||
import Proxyable from 'vuetify/lib/mixins/proxyable'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MaterialDropdown',
|
|
||||||
|
|
||||||
mixins: [Proxyable],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: 'primary'
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
default: () => ([
|
|
||||||
{
|
|
||||||
id: undefined,
|
|
||||||
text: undefined
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
transition: {
|
|
||||||
type: String,
|
|
||||||
default: 'scale-transition'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,66 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-snackbar
|
|
||||||
v-bind="{
|
|
||||||
...$attrs,
|
|
||||||
...$props,
|
|
||||||
'color': 'transparent'
|
|
||||||
}"
|
|
||||||
:class="classes"
|
|
||||||
:value="value"
|
|
||||||
@change="$emit('change', $event)"
|
|
||||||
>
|
|
||||||
<base-material-alert
|
|
||||||
:color="color"
|
|
||||||
:dismissible="dismissible"
|
|
||||||
:type="type"
|
|
||||||
class="ma-0"
|
|
||||||
dark
|
|
||||||
>
|
|
||||||
<slot/>
|
|
||||||
</base-material-alert>
|
|
||||||
</v-snackbar>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
// Components
|
|
||||||
import {VSnackbar} from 'vuetify/lib'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'BaseMaterialSnackbar',
|
|
||||||
|
|
||||||
extends: VSnackbar,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
dismissible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
classes() {
|
|
||||||
return {
|
|
||||||
...VSnackbar.options.computed.classes.call(this),
|
|
||||||
'v-snackbar--material': true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.v-snackbar--material
|
|
||||||
margin-top: 32px
|
|
||||||
margin-bottom: 32px
|
|
||||||
|
|
||||||
.v-alert--material,
|
|
||||||
.v-snack__wrapper
|
|
||||||
border-radius: 4px
|
|
||||||
|
|
||||||
.v-snack__content
|
|
||||||
overflow: visible
|
|
||||||
padding: 0
|
|
||||||
</style>
|
|
@ -1,113 +0,0 @@
|
|||||||
<template>
|
|
||||||
<base-material-card
|
|
||||||
v-bind="$attrs"
|
|
||||||
v-on="$listeners"
|
|
||||||
:icon="icon"
|
|
||||||
class="v-card--material-stats"
|
|
||||||
>
|
|
||||||
<template v-slot:after-heading>
|
|
||||||
<div class="ml-auto text-right">
|
|
||||||
<div
|
|
||||||
class="body-3 grey--text font-weight-light"
|
|
||||||
v-text="title"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h3 class="display-2 font-weight-light text--primary">
|
|
||||||
{{ value }} <small>{{ smallValue }}</small>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
class="px-0"
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<v-divider/>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-icon
|
|
||||||
:color="subIconColor"
|
|
||||||
class="ml-2 mr-1"
|
|
||||||
size="16"
|
|
||||||
>
|
|
||||||
{{ subIcon }}
|
|
||||||
</v-icon>
|
|
||||||
|
|
||||||
<span
|
|
||||||
:class="subTextColor"
|
|
||||||
class="caption grey--text font-weight-light"
|
|
||||||
v-text="subText"
|
|
||||||
/>
|
|
||||||
</base-material-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Card from './Card'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MaterialStatsCard',
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
...Card.props,
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
subIcon: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
subIconColor: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
subTextColor: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
subText: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
smallValue: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.v-card--material-stats
|
|
||||||
display: flex
|
|
||||||
flex-wrap: wrap
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
> div:first-child
|
|
||||||
justify-content: space-between
|
|
||||||
|
|
||||||
.v-card
|
|
||||||
border-radius: 4px
|
|
||||||
flex: 0 1 auto
|
|
||||||
|
|
||||||
.v-card__text
|
|
||||||
display: inline-block
|
|
||||||
flex: 1 0 calc(100% - 120px)
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
right: 0
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
.v-card__actions
|
|
||||||
flex: 1 0 100%
|
|
||||||
</style>
|
|
@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-tabs
|
|
||||||
v-model="internalValue"
|
|
||||||
v-bind="$attrs"
|
|
||||||
:active-class="`${color} ${$vuetify.theme.dark ? 'black' : 'white'}--text`"
|
|
||||||
class="v-tabs--pill"
|
|
||||||
hide-slider
|
|
||||||
>
|
|
||||||
<slot/>
|
|
||||||
|
|
||||||
<slot name="items"/>
|
|
||||||
</v-tabs>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Mixins
|
|
||||||
import Proxyable from 'vuetify/lib/mixins/proxyable'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MaterialTabs',
|
|
||||||
|
|
||||||
mixins: [Proxyable],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: 'primary'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.v-tabs--pill
|
|
||||||
.v-tab,
|
|
||||||
.v-tab:before
|
|
||||||
border-radius: 24px
|
|
||||||
|
|
||||||
&.v-tabs--icons-and-text
|
|
||||||
.v-tab,
|
|
||||||
.v-tab:before
|
|
||||||
border-radius: 4px
|
|
||||||
</style>
|
|
@ -1,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card class="text-center v-card--testimony">
|
|
||||||
<div class="pt-6">
|
|
||||||
<v-icon
|
|
||||||
color="black"
|
|
||||||
x-large
|
|
||||||
>
|
|
||||||
mdi-format-quote-close
|
|
||||||
</v-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-card-text
|
|
||||||
class="display-1 font-weight-light font-italic mb-3"
|
|
||||||
v-text="blurb"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="display-2 font-weight-light mb-2"
|
|
||||||
v-text="author"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="body-2 text-uppercase grey--text"
|
|
||||||
v-text="handle"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<v-avatar
|
|
||||||
color="grey"
|
|
||||||
size="100"
|
|
||||||
>
|
|
||||||
<v-img
|
|
||||||
:alt="`${author} Testimonial`"
|
|
||||||
:src="avatar"
|
|
||||||
/>
|
|
||||||
</v-avatar>
|
|
||||||
|
|
||||||
<div/>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'BaseMaterialTestimony',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
author: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
type: String,
|
|
||||||
default: 'https://demos.creative-tim.com/material-dashboard-pro/assets/img/faces/card-profile1-square.jpg'
|
|
||||||
},
|
|
||||||
blurb: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
handle: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.v-card--testimony
|
|
||||||
padding-bottom: 72px
|
|
||||||
margin-bottom: 64px
|
|
||||||
|
|
||||||
.v-avatar
|
|
||||||
position: absolute
|
|
||||||
left: calc(50% - 64px)
|
|
||||||
top: calc(100% - 64px)
|
|
||||||
</style>
|
|
@ -1,109 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card
|
|
||||||
class="v-card--wizard"
|
|
||||||
elevation="12"
|
|
||||||
max-width="700"
|
|
||||||
>
|
|
||||||
<v-card-title class="justify-center display-2 font-weight-light pt-5">
|
|
||||||
Build your profile
|
|
||||||
</v-card-title>
|
|
||||||
|
|
||||||
<div class="text-center display-1 grey--text font-weight-light mb-6">
|
|
||||||
This information will let us know more about you.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-tabs
|
|
||||||
ref="tabs"
|
|
||||||
v-model="internalValue"
|
|
||||||
background-color="green lighten-5"
|
|
||||||
color="white"
|
|
||||||
grow
|
|
||||||
slider-size="50"
|
|
||||||
>
|
|
||||||
<v-tabs-slider
|
|
||||||
class="mt-1"
|
|
||||||
color="success"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<v-tab
|
|
||||||
v-for="(item, i) in items"
|
|
||||||
:key="i"
|
|
||||||
:disabled="!availableSteps.includes(i)"
|
|
||||||
:ripple="false"
|
|
||||||
>
|
|
||||||
{{ item }}
|
|
||||||
</v-tab>
|
|
||||||
</v-tabs>
|
|
||||||
|
|
||||||
<div class="my-6"/>
|
|
||||||
|
|
||||||
<v-card-text>
|
|
||||||
<v-tabs-items v-model="internalValue">
|
|
||||||
<slot/>
|
|
||||||
</v-tabs-items>
|
|
||||||
</v-card-text>
|
|
||||||
|
|
||||||
<v-card-actions class="pb-4 pa-4">
|
|
||||||
<v-btn
|
|
||||||
:disabled="internalValue === 0"
|
|
||||||
class="white--text"
|
|
||||||
color="grey darken-2"
|
|
||||||
min-width="125"
|
|
||||||
@click="$emit('click:prev')"
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<v-spacer/>
|
|
||||||
|
|
||||||
<v-btn
|
|
||||||
:disabled="!availableSteps.includes(internalValue + 1)"
|
|
||||||
color="success"
|
|
||||||
min-width="100"
|
|
||||||
@click="$emit('click:next')"
|
|
||||||
>
|
|
||||||
{{ internalValue === items.length - 1 ? 'Finish' : 'Next' }}
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Mixins
|
|
||||||
import Proxyable from 'vuetify/lib/mixins/proxyable'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'BaseMaterialWizard',
|
|
||||||
|
|
||||||
mixins: [Proxyable],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
availableSteps: {
|
|
||||||
type: Array,
|
|
||||||
default: () => ([])
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
default: () => ([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.v-card--wizard
|
|
||||||
overflow: visible
|
|
||||||
|
|
||||||
.v-tabs-bar
|
|
||||||
height: 56px
|
|
||||||
padding: 0 8px
|
|
||||||
|
|
||||||
.v-slide-group__wrapper
|
|
||||||
overflow: visible
|
|
||||||
|
|
||||||
.v-tabs-slider
|
|
||||||
border-radius: 4px
|
|
||||||
|
|
||||||
.v-slide-group__wrapper
|
|
||||||
contain: initial
|
|
||||||
</style>
|
|
@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="display-2 font-weight-light col col-12 text-left text--primary pa-0 mb-8">
|
|
||||||
<h5 class="font-weight-light">
|
|
||||||
{{ subheading }}
|
|
||||||
<template v-if="text">
|
|
||||||
<span
|
|
||||||
class="subtitle-1"
|
|
||||||
v-text="text"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="pt-2">
|
|
||||||
<slot/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'Subheading',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
subheading: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="mb-12 text-center">
|
|
||||||
<h1
|
|
||||||
class="font-weight-light mb-2"
|
|
||||||
style="color:#3c4858; font-size:24px"
|
|
||||||
v-text="`Vuetify ${heading}`"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="font-weight-light"
|
|
||||||
style="font-size: 16px; color: #3c4858"
|
|
||||||
>
|
|
||||||
Please checkout the
|
|
||||||
<a
|
|
||||||
:href="`https://vuetifyjs.com/${link}`"
|
|
||||||
class="secondary--text"
|
|
||||||
rel="noopener"
|
|
||||||
style="text-decoration:none;"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
full documentation
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'VComponent',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
heading: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,4 +0,0 @@
|
|||||||
const DEBUG = process.env.NODE_ENV === 'development';
|
|
||||||
const domain = process.env.DOMIAN || 'https://sub.store';
|
|
||||||
export const BACKEND_BASE = DEBUG ? `http://localhost:3000` : domain;
|
|
||||||
// export const BACKEND_BASE = DEBUG ? `https://sub.store:9999` : `https://sub.store`;
|
|
@ -1,24 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import VueI18n from 'vue-i18n'
|
|
||||||
|
|
||||||
import ar from 'vuetify/lib/locale/ar'
|
|
||||||
import en from 'vuetify/lib/locale/en'
|
|
||||||
|
|
||||||
Vue.use(VueI18n)
|
|
||||||
|
|
||||||
const messages = {
|
|
||||||
en: {
|
|
||||||
...require('@/locales/en.json'),
|
|
||||||
$vuetify: en
|
|
||||||
},
|
|
||||||
ar: {
|
|
||||||
...require('@/locales/ar.json'),
|
|
||||||
$vuetify: ar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new VueI18n({
|
|
||||||
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
|
|
||||||
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
|
|
||||||
messages
|
|
||||||
})
|
|
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"avatar": "تانيا أندرو",
|
|
||||||
"buttons": "وصفت",
|
|
||||||
"calendar": "التقويم",
|
|
||||||
"charts": "الرسوم البيانية",
|
|
||||||
"components": "المكونات",
|
|
||||||
"ct": "CT",
|
|
||||||
"dashboard": "لوحة القيادة",
|
|
||||||
"dtables": "جداول البيانات",
|
|
||||||
"eforms": "أشكال موسعة",
|
|
||||||
"error": "صفحة الخطأ",
|
|
||||||
"etables": "الجداول الموسعة",
|
|
||||||
"example": "مثال",
|
|
||||||
"forms": "إستمارات",
|
|
||||||
"fullscreen": "خريطة الشاشة الكاملة",
|
|
||||||
"google": "خرائط جوجل",
|
|
||||||
"grid": "نظام الشبكة",
|
|
||||||
"icons": "الرموز",
|
|
||||||
"lock": "قفل الشاشة الصفحة",
|
|
||||||
"login": "صفحة تسجيل الدخول",
|
|
||||||
"maps": "خرائط",
|
|
||||||
"multi": "متعدد المستويات انهيار",
|
|
||||||
"notifications": "إخطارات",
|
|
||||||
"pages": "صفحات",
|
|
||||||
"plan": "اختر خطة",
|
|
||||||
"pricing": "التسعير",
|
|
||||||
"my-profile": "ملفي",
|
|
||||||
"edit-profile": "تعديل الملف الشخصي",
|
|
||||||
"register": "تسجيل الصفحة",
|
|
||||||
"rforms": "النماذج العادية",
|
|
||||||
"rtables": "الجداول العادية",
|
|
||||||
"rtl": "دعم RTL",
|
|
||||||
"search": "بحث...",
|
|
||||||
"settings": "الإعدادات",
|
|
||||||
"tables": "الجداول",
|
|
||||||
"tabs": "Tabs",
|
|
||||||
"tim": "تيم الإبداعية",
|
|
||||||
"timeline": "الجدول الزمني",
|
|
||||||
"typography": "طباعة",
|
|
||||||
"user": "ملف تعريفي للمستخدم",
|
|
||||||
"vforms": "نماذج التحقق من الصحة",
|
|
||||||
"widgets": "الحاجيات",
|
|
||||||
"wizard": "ساحر"
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"avatar": "Tania Andrew",
|
|
||||||
"buttons": "Buttons",
|
|
||||||
"calendar": "Calendar",
|
|
||||||
"charts": "Charts",
|
|
||||||
"components": "Components",
|
|
||||||
"ct": "CT",
|
|
||||||
"dashboard": "Dashboard",
|
|
||||||
"dtables": "Data Tables",
|
|
||||||
"eforms": "Extended Forms",
|
|
||||||
"error": "Error Page",
|
|
||||||
"etables": "Extended Tables",
|
|
||||||
"example": "Example",
|
|
||||||
"forms": "Forms",
|
|
||||||
"fullscreen": "Full Screen Map",
|
|
||||||
"google": "Google Maps",
|
|
||||||
"grid": "Grid System",
|
|
||||||
"icons": "Icons",
|
|
||||||
"lock": "Lock Screen Page",
|
|
||||||
"login": "Login Page",
|
|
||||||
"maps": "Maps",
|
|
||||||
"multi": "Multi Level Collapse",
|
|
||||||
"notifications": "Notifications",
|
|
||||||
"pages": "Pages",
|
|
||||||
"plan": "Choose Plan",
|
|
||||||
"pricing": "Pricing",
|
|
||||||
"my-profile": "My Profile",
|
|
||||||
"edit-profile": "Edit Profile",
|
|
||||||
"register": "Register Page",
|
|
||||||
"rforms": "Regular Forms",
|
|
||||||
"rtables": "Regular Tables",
|
|
||||||
"rtl": "RTL Support",
|
|
||||||
"search": "Search",
|
|
||||||
"settings": "Settings",
|
|
||||||
"tables": "Tables",
|
|
||||||
"tabs": "Tabs",
|
|
||||||
"tim": "Creative Tim",
|
|
||||||
"timeline": "Timeline",
|
|
||||||
"typography": "Typography",
|
|
||||||
"user": "User Profile",
|
|
||||||
"vforms": "Validation Forms",
|
|
||||||
"widgets": "Widgets",
|
|
||||||
"wizard": "Wizard"
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import vuetify from './plugins/vuetify';
|
|
||||||
import 'material-design-icons-iconfont/dist/material-design-icons.css'
|
|
||||||
import './plugins/base';
|
|
||||||
import './plugins/chartist';
|
|
||||||
// import './plugins/vee-validate';
|
|
||||||
import './plugins/vue-world-map';
|
|
||||||
import i18n from './i18n';
|
|
||||||
import router from './router';
|
|
||||||
import store from './store';
|
|
||||||
import Clipboard from 'v-clipboard';
|
|
||||||
import App from './App.vue'
|
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
|
||||||
Vue.use(Clipboard);
|
|
||||||
new Vue({
|
|
||||||
vuetify,
|
|
||||||
router,
|
|
||||||
store,
|
|
||||||
Clipboard,
|
|
||||||
i18n,
|
|
||||||
render: h => h(App)
|
|
||||||
}).$mount('#app')
|
|
@ -1,17 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import upperFirst from 'lodash/upperFirst'
|
|
||||||
import camelCase from 'lodash/camelCase'
|
|
||||||
|
|
||||||
const requireComponent = require.context(
|
|
||||||
'@/components/base', true, /\.vue$/
|
|
||||||
)
|
|
||||||
|
|
||||||
requireComponent.keys().forEach(fileName => {
|
|
||||||
const componentConfig = requireComponent(fileName)
|
|
||||||
|
|
||||||
const componentName = upperFirst(
|
|
||||||
camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, ''))
|
|
||||||
)
|
|
||||||
|
|
||||||
Vue.component(`Base${componentName}`, componentConfig.default || componentConfig)
|
|
||||||
})
|
|
@ -1,4 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import 'chartist/dist/chartist.min.css'
|
|
||||||
|
|
||||||
Vue.use(require('vue-chartist'))
|
|
@ -1,5 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
// import * as VeeValidate from 'vee-validate'
|
|
||||||
import VeeValidate from 'vee-validate'
|
|
||||||
|
|
||||||
Vue.use(VeeValidate)
|
|
@ -1,4 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import VueWorldMap from 'vue-world-map'
|
|
||||||
|
|
||||||
Vue.component('v-world-map', VueWorldMap)
|
|
@ -1,24 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import Vuetify from 'vuetify/lib'
|
|
||||||
import i18n from '@/i18n'
|
|
||||||
|
|
||||||
Vue.use(Vuetify)
|
|
||||||
|
|
||||||
const theme = {
|
|
||||||
primary: '#E91E63',
|
|
||||||
secondary: '#9C27b0',
|
|
||||||
accent: '#9C27b0',
|
|
||||||
info: '#00CAE3'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new Vuetify({
|
|
||||||
lang: {
|
|
||||||
t: (key, ...params) => i18n.t(key, params)
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
themes: {
|
|
||||||
dark: theme,
|
|
||||||
light: theme
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,62 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
import Router from 'vue-router';
|
|
||||||
import store from "../store";
|
|
||||||
|
|
||||||
import Subscription from "@/views/Subscription";
|
|
||||||
import Dashboard from "@/views/Dashboard";
|
|
||||||
import User from "@/views/User";
|
|
||||||
import SubEditor from "@/views/SubEditor";
|
|
||||||
import Cloud from "@/views/Cloud";
|
|
||||||
|
|
||||||
Vue.use(Router);
|
|
||||||
|
|
||||||
const router = new Router({
|
|
||||||
base: process.env.BASE_URL,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
name: "subscriptions",
|
|
||||||
component: Subscription,
|
|
||||||
meta: {title: "订阅", keepAlive: true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/dashboard",
|
|
||||||
name: "dashboard",
|
|
||||||
component: Dashboard,
|
|
||||||
meta: {title: "首页", keepAlive: true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/cloud",
|
|
||||||
name: "artifact",
|
|
||||||
component: Cloud,
|
|
||||||
meta: {title: "同步", keepAlive: true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/user",
|
|
||||||
name: "user",
|
|
||||||
component: User,
|
|
||||||
meta: {title: "我的", keepAlive: true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/sub-edit/:name",
|
|
||||||
name: "sub-editor",
|
|
||||||
component: SubEditor,
|
|
||||||
meta: {title: "订阅编辑"}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/collection-edit/:name",
|
|
||||||
name: "collection-edit",
|
|
||||||
component: SubEditor,
|
|
||||||
props: {isCollection: true},
|
|
||||||
meta: {title: "组合订阅编辑"}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
const {meta} = to;
|
|
||||||
store.commit("SET_NAV_TITLE", meta.title);
|
|
||||||
next();
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router;
|
|
@ -1,137 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
import Vuex from 'vuex';
|
|
||||||
import {axios} from "@/utils";
|
|
||||||
|
|
||||||
Vue.use(Vuex);
|
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
|
||||||
state: {
|
|
||||||
title: "Sub-Store",
|
|
||||||
clipboard: "",
|
|
||||||
isLoading: false,
|
|
||||||
|
|
||||||
bottomNavBarHeight: 0,
|
|
||||||
|
|
||||||
successMessage: "",
|
|
||||||
errorMessage: "",
|
|
||||||
snackbarTimer: "",
|
|
||||||
|
|
||||||
subscriptions: {},
|
|
||||||
collections: {},
|
|
||||||
artifacts: {},
|
|
||||||
env: {},
|
|
||||||
settings: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
mutations: {
|
|
||||||
COPY(state, text) {
|
|
||||||
state.clipboard = text;
|
|
||||||
},
|
|
||||||
// UI
|
|
||||||
SET_NAV_TITLE(state, title) {
|
|
||||||
state.title = title;
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_BOTTOM_NAVBAR_HEIGHT (state, height){
|
|
||||||
state.bottomNavBarHeight = height;
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_LOADING(state, loading) {
|
|
||||||
state.isLoading = loading;
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_SNACK_BAR_TIMER(state, timer) {
|
|
||||||
state.snackbarTimer = timer;
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_SUCCESS_MESSAGE(state, msg) {
|
|
||||||
state.successMessage = msg;
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_ERROR_MESSAGE(state, msg) {
|
|
||||||
state.errorMessage = msg;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// fetch subscriptions
|
|
||||||
async FETCH_SUBSCRIPTIONS({state}) {
|
|
||||||
return axios.get("/subs").then(resp => {
|
|
||||||
const {data} = resp.data;
|
|
||||||
state.subscriptions = data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// fetch collections
|
|
||||||
async FETCH_COLLECTIONS({state}) {
|
|
||||||
return axios.get("/collections").then(resp => {
|
|
||||||
const {data} = resp.data;
|
|
||||||
state.collections = data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async FETCH_ARTIFACTS({state}) {
|
|
||||||
return axios.get("/artifacts").then(resp => {
|
|
||||||
const {data} = resp.data;
|
|
||||||
state.artifacts = data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// fetch env
|
|
||||||
async FETCH_ENV({state}) {
|
|
||||||
return axios.get("/utils/env").then(resp => {
|
|
||||||
const {data} = resp.data;
|
|
||||||
state.env = data;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async FETCH_SETTINGS({state}) {
|
|
||||||
return axios.get("/settings").then(resp => {
|
|
||||||
state.settings = {
|
|
||||||
theme: {
|
|
||||||
darkMode: false
|
|
||||||
},
|
|
||||||
...resp.data
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// update subscriptions
|
|
||||||
async UPDATE_SUBSCRIPTION({dispatch}, {name, sub}) {
|
|
||||||
return axios.patch(`/sub/${name}`, sub).then(() => {
|
|
||||||
dispatch("FETCH_SUBSCRIPTIONS");
|
|
||||||
dispatch("FETCH_COLLECTIONS");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// new subscription
|
|
||||||
async NEW_SUBSCRIPTION({dispatch}, sub) {
|
|
||||||
return axios.post(`/subs`, sub).then(() => {
|
|
||||||
dispatch("FETCH_SUBSCRIPTIONS");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// delete subscription
|
|
||||||
async DELETE_SUBSCRIPTION({dispatch}, name) {
|
|
||||||
return axios.delete(`/sub/${name}`).then(() => {
|
|
||||||
dispatch("FETCH_SUBSCRIPTIONS");
|
|
||||||
dispatch("FETCH_COLLECTIONS");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// update collection
|
|
||||||
async UPDATE_COLLECTION({dispatch}, {name, collection}) {
|
|
||||||
return axios.patch(`/collection/${name}`, collection).then(() => {
|
|
||||||
dispatch("FETCH_COLLECTIONS");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// new collection
|
|
||||||
async NEW_COLLECTION({dispatch}, collection) {
|
|
||||||
return axios.post(`/collections`, collection).then(() => {
|
|
||||||
dispatch("FETCH_COLLECTIONS");
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// delete collection
|
|
||||||
async DELETE_COLLECTION({dispatch}, name) {
|
|
||||||
return axios.delete(`/collection/${name}`).then(() => {
|
|
||||||
dispatch("FETCH_COLLECTIONS");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default store;
|
|
@ -1,23 +0,0 @@
|
|||||||
import Axios from 'axios';
|
|
||||||
import Vue from 'vue';
|
|
||||||
import store from "@/store";
|
|
||||||
import {BACKEND_BASE} from "@/config";
|
|
||||||
|
|
||||||
export const axios = Axios.create({
|
|
||||||
baseURL: `${BACKEND_BASE}/api`,
|
|
||||||
timeout: 10000
|
|
||||||
});
|
|
||||||
|
|
||||||
export const EventBus = new Vue();
|
|
||||||
|
|
||||||
export function isEmptyObj(obj) {
|
|
||||||
return Object.keys(obj).length === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function showInfo(msg) {
|
|
||||||
store.commit("SET_SUCCESS_MESSAGE", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function showError(err) {
|
|
||||||
store.commit("SET_ERROR_MESSAGE", err);
|
|
||||||
}
|
|
@ -1,345 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container fluid>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon left>mdi-cloud</v-icon>
|
|
||||||
同步配置
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="syncAllArtifacts()">
|
|
||||||
<v-icon>backup</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click="openGist()">
|
|
||||||
<v-icon>visibility</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-dialog v-model="showArtifactDialog" max-width="400px">
|
|
||||||
<template #activator="{on}">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon color="primary">mdi-plus-circle</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card class="pl-4 pr-4 pb-4 pt-4">
|
|
||||||
<v-subheader>
|
|
||||||
<v-icon left>{{ editing ? 'edit_off' : 'mdi-plus-circle' }}</v-icon>
|
|
||||||
<h3>{{ editing ? '修改' : '添加' }}同步配置</h3>
|
|
||||||
</v-subheader>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-form v-model="formValid" class="pt-4 pl-4 pr-4 pb-0">
|
|
||||||
<v-text-field v-model="currentArtifact.name" :disabled="editing" clear-icon="clear" clearable label="配置名称"
|
|
||||||
placeholder="填入生成配置名称,名称需唯一,如Clash.yaml。" />
|
|
||||||
<v-text-field v-model="currentArtifact['display-name']" clear-icon="clear" clearable label="配置显示名称"
|
|
||||||
placeholder="填入生成配置显示名称,名称无需唯一。" />
|
|
||||||
<v-menu offset-y>
|
|
||||||
<template v-slot:activator="{on}">
|
|
||||||
<v-text-field v-on="on" :rules="validations.required" :value="getType(currentArtifact.type)"
|
|
||||||
label="类型" />
|
|
||||||
</template>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-item @click="setArtifactType('subscription')">
|
|
||||||
<v-list-item-icon>
|
|
||||||
<v-icon>mdi-link</v-icon>
|
|
||||||
</v-list-item-icon>
|
|
||||||
<v-list-item-title>订阅</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item @click="setArtifactType('collection')">
|
|
||||||
<v-list-item-icon>
|
|
||||||
<v-icon>list</v-icon>
|
|
||||||
</v-list-item-icon>
|
|
||||||
<v-list-item-title>组合订阅</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
<v-menu offset-y>
|
|
||||||
<template v-slot:activator="{on}">
|
|
||||||
<v-text-field v-model="currentArtifact.source" v-on="on"
|
|
||||||
:placeholder="`填入${getType(currentArtifact.type) || '来源'}的名称。`" :rules="validations.required"
|
|
||||||
label="来源" />
|
|
||||||
</template>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-item v-for="(sub, idx) in getSources(currentArtifact.type)" :key="idx"
|
|
||||||
@click="currentArtifact.source = sub.name">
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-icon v-if="!sub.icon" color="teal darken-1">mdi-cloud</v-icon>
|
|
||||||
<v-img v-else :class="getIconClass(sub.icon)" :src="sub.icon" />
|
|
||||||
</v-list-item-avatar>
|
|
||||||
<v-list-item-title>{{ sub['display-name'] || sub.name }}</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
|
|
||||||
<v-menu offset-y>
|
|
||||||
<template v-slot:activator="{on}">
|
|
||||||
<v-text-field v-on="on" :rules="validations.required" :value="currentArtifact.platform" label="目标" />
|
|
||||||
</template>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-item v-for="platform in ['Surge', 'Loon', 'QX', 'Clash']" :key="platform"
|
|
||||||
@click="currentArtifact.platform = platform">
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-img :class="getIconClass('#invert')" :src="getIcon(platform)"></v-img>
|
|
||||||
</v-list-item-avatar>
|
|
||||||
<v-list-item-title>{{ platform }}</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</v-form>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn :disabled="!formValid" color="primary" small text @click="doneEditArtifact()">
|
|
||||||
确认
|
|
||||||
</v-btn>
|
|
||||||
<v-btn small text @click="clear()">
|
|
||||||
取消
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card-title>
|
|
||||||
|
|
||||||
<template v-for="(artifact, idx) in artifacts">
|
|
||||||
<v-list-item :key="artifact.name" dense three-line>
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-img :class="getIconClass('#invert')" :src="getIcon(artifact.platform)" />
|
|
||||||
</v-list-item-avatar>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>
|
|
||||||
{{ artifact['display-name'] || artifact.name }}
|
|
||||||
</v-list-item-title>
|
|
||||||
<v-chip-group>
|
|
||||||
<v-chip label>
|
|
||||||
<v-icon left>info</v-icon>
|
|
||||||
{{ getType(artifact.type) }}
|
|
||||||
</v-chip>
|
|
||||||
<v-chip label>
|
|
||||||
<v-icon left>mdi-link</v-icon>
|
|
||||||
{{ artifact.source }}
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
<v-list-item-subtitle>更新于:{{ getUpdatedTime(artifact.updated) }}</v-list-item-subtitle>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-list-item-action>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-btn icon @click="toggleSync(artifact)">
|
|
||||||
<v-icon :color="artifact.sync ? undefined: 'red'">{{ artifact.sync ? "alarm" : "alarm_off" }}</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-menu bottom left>
|
|
||||||
<template #activator="{ on }">
|
|
||||||
<v-btn v-on="on" icon>
|
|
||||||
<v-icon>mdi-dots-vertical</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-item v-if="artifact.url" @click="copy(artifact)">
|
|
||||||
<v-list-item-title>复制</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item @click="editArtifact(artifact)">
|
|
||||||
<v-list-item-title>编辑</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item @click="preview(artifact.name)">
|
|
||||||
<v-list-item-title>预览</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item @click="sync(artifact.name)">
|
|
||||||
<v-list-item-title>同步</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item @click="deleteArtifact(idx, artifact.name)">
|
|
||||||
<v-list-item-title>删除</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-list-item-action>
|
|
||||||
</v-list-item>
|
|
||||||
</template>
|
|
||||||
</v-card>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {axios} from '@/utils';
|
|
||||||
import {BACKEND_BASE} from "@/config";
|
|
||||||
import {format} from 'timeago.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "Cloud",
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showArtifactDialog: false,
|
|
||||||
currentArtifact: {
|
|
||||||
name: "",
|
|
||||||
'display-name': "",
|
|
||||||
type: "subscription",
|
|
||||||
source: "",
|
|
||||||
platform: "",
|
|
||||||
},
|
|
||||||
editing: null,
|
|
||||||
formValid: false,
|
|
||||||
validations: {
|
|
||||||
required: [
|
|
||||||
v => !!v || "不能为空!"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
artifacts() {
|
|
||||||
const items = this.$store.state.artifacts;
|
|
||||||
return Object.keys(items).map(k => items[k]);
|
|
||||||
},
|
|
||||||
settings() {
|
|
||||||
return this.$store.state.settings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getIcon(platform) {
|
|
||||||
const ICONS = {
|
|
||||||
"Clash": "https://raw.githubusercontent.com/58xinian/icon/master/clash_mini.png",
|
|
||||||
"QX": "https://raw.githubusercontent.com/Orz-3/mini/none/quanX.png",
|
|
||||||
"Surge": "https://raw.githubusercontent.com/Orz-3/mini/none/surge.png",
|
|
||||||
"Loon": "https://raw.githubusercontent.com/Orz-3/mini/none/loon.png",
|
|
||||||
"ShadowRocket": "https://raw.githubusercontent.com/Orz-3/mini/master/loon.png"
|
|
||||||
}
|
|
||||||
return ICONS[platform];
|
|
||||||
},
|
|
||||||
|
|
||||||
getType(type) {
|
|
||||||
const DESCRIPTIONS = {
|
|
||||||
"subscription": "订阅",
|
|
||||||
"collection": "组合订阅"
|
|
||||||
}
|
|
||||||
return DESCRIPTIONS[type];
|
|
||||||
},
|
|
||||||
|
|
||||||
getUpdatedTime(time) {
|
|
||||||
if (!time) {
|
|
||||||
return "从未更新";
|
|
||||||
} else {
|
|
||||||
return format(time, "zh_CN");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async doneEditArtifact() {
|
|
||||||
console.log(JSON.stringify(this.currentArtifact, null, 2));
|
|
||||||
try {
|
|
||||||
if (this.editing) {
|
|
||||||
await axios.patch(`/artifact/${this.currentArtifact.name}`, this.currentArtifact);
|
|
||||||
} else {
|
|
||||||
await axios.post("/artifacts", this.currentArtifact);
|
|
||||||
}
|
|
||||||
await this.$store.dispatch("FETCH_ARTIFACTS");
|
|
||||||
this.clear();
|
|
||||||
} catch (err) {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", `${this.editing ? "更新" : "创建"}配置失败!${err}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async editArtifact(artifact) {
|
|
||||||
this.editing = true;
|
|
||||||
Object.assign(this.currentArtifact, artifact);
|
|
||||||
this.showArtifactDialog = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async toggleSync(artifact) {
|
|
||||||
artifact.sync = !artifact.sync;
|
|
||||||
try {
|
|
||||||
await axios.patch(`/artifact/${artifact.name}`, artifact);
|
|
||||||
await this.$store.dispatch("FETCH_ARTIFACTS");
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", `已${artifact.sync ? '启用' : '禁用'}自动同步配置${artifact.name}`);
|
|
||||||
} catch (err) {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", `更改同步配置失败!${err}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteArtifact(idx, name) {
|
|
||||||
try {
|
|
||||||
await axios.delete(`/artifact/${name}`);
|
|
||||||
await this.$store.dispatch("FETCH_ARTIFACTS");
|
|
||||||
} catch (err) {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", `删除配置失败!${err}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.currentArtifact = {
|
|
||||||
name: "",
|
|
||||||
'display-name': "",
|
|
||||||
type: "subscription",
|
|
||||||
source: "",
|
|
||||||
platform: ""
|
|
||||||
}
|
|
||||||
this.showArtifactDialog = false;
|
|
||||||
this.editing = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
copy(artifact) {
|
|
||||||
if (artifact.url) {
|
|
||||||
this.$clipboard(artifact.url);
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", "成功复制配置链接");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
preview(name) {
|
|
||||||
window.open(`${BACKEND_BASE}/api/artifact/${name}?action=preview`);
|
|
||||||
},
|
|
||||||
|
|
||||||
async sync(name) {
|
|
||||||
this.$store.commit("SET_LOADING", true);
|
|
||||||
try {
|
|
||||||
await axios.get(`/artifact/${name}?action=sync`);
|
|
||||||
await this.$store.dispatch("FETCH_ARTIFACTS");
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", `同步配置成功!`);
|
|
||||||
} catch (err) {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", `同步配置失败!${err}`);
|
|
||||||
} finally {
|
|
||||||
this.$store.commit("SET_LOADING", false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async syncAllArtifacts() {
|
|
||||||
this.$store.commit("SET_LOADING", true);
|
|
||||||
try {
|
|
||||||
await axios.get(`/cron/sync-artifacts`);
|
|
||||||
await this.$store.dispatch("FETCH_ARTIFACTS");
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", `Gist 同步生成节点成功!`);
|
|
||||||
} catch (err) {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", `Gist 同步生成节点失败!${err}`);
|
|
||||||
} finally {
|
|
||||||
this.$store.commit("SET_LOADING", false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setArtifactType(type) {
|
|
||||||
this.currentArtifact.type = type;
|
|
||||||
this.currentArtifact.source = "";
|
|
||||||
},
|
|
||||||
|
|
||||||
getSources(type) {
|
|
||||||
let data;
|
|
||||||
switch (type) {
|
|
||||||
case "subscription":
|
|
||||||
data = this.$store.state.subscriptions;
|
|
||||||
break;
|
|
||||||
case "collection":
|
|
||||||
data = this.$store.state.collections;
|
|
||||||
}
|
|
||||||
return Object.keys(data).map(k => data[k]);
|
|
||||||
},
|
|
||||||
|
|
||||||
getIconClass(url) {
|
|
||||||
return url.indexOf('#invert') !== -1 && !this.$vuetify.theme.dark ? 'invert' : ''
|
|
||||||
},
|
|
||||||
|
|
||||||
openGist() {
|
|
||||||
window.open(`https://gist.github.com${'/' + this.settings.githubUser || ''}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.invert {
|
|
||||||
filter: invert(100%);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,49 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container></v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "Dashboard",
|
|
||||||
components: {},
|
|
||||||
computed: {
|
|
||||||
pie() {
|
|
||||||
const total = 400;
|
|
||||||
const upload = 30;
|
|
||||||
const download = 200;
|
|
||||||
const remaining = total - (upload + download);
|
|
||||||
return {
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: "流量",
|
|
||||||
type: "pie",
|
|
||||||
radius: "50%",
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
name: `剩余量\n${(remaining)} GB`,
|
|
||||||
value: remaining
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `下载量\n${(download)} GB`,
|
|
||||||
value: download
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `上传量\n${(upload)} GB`,
|
|
||||||
value: upload
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
animationEasing: 'elasticOut'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.remains {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px; /* or e.g. 400px */
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,752 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container>
|
|
||||||
<FloatMenu class="floatActionBtn">
|
|
||||||
<v-btn fab small @click.stop="dialog = true">
|
|
||||||
<v-icon color="purple lighten-1">mdi-plus-circle</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn fab small @click="save">
|
|
||||||
<v-icon color="teal lighten-1">mdi-content-save</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</FloatMenu>
|
|
||||||
|
|
||||||
<v-dialog v-model="dialog" scrollable>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>选择节点操作</v-card-title>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-card-text>
|
|
||||||
<v-radio-group v-model="selectedProcess" dense>
|
|
||||||
<v-radio
|
|
||||||
v-for="(k, idx) in Object.keys(availableProcessors)"
|
|
||||||
:key="idx"
|
|
||||||
:label="availableProcessors[k].name"
|
|
||||||
:value="k"
|
|
||||||
></v-radio>
|
|
||||||
</v-radio-group>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn color="primary" text @click="addProcess(selectedProcess)"
|
|
||||||
>确认
|
|
||||||
</v-btn>
|
|
||||||
<v-btn text @click="dialog = false">取消</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
|
|
||||||
<v-dialog v-model = "showShareDialog" max-width = "400px">
|
|
||||||
<v-card class = "pl-4 pr-4 pb-4 pt-4">
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon left>mdi-file-import</v-icon>
|
|
||||||
配置导入
|
|
||||||
<v-spacer />
|
|
||||||
</v-card-title>
|
|
||||||
<v-textarea v-model = "imported" :rules = "validations.importRules"
|
|
||||||
clear-icon = "clear" clearable label = "粘贴配置以导入" rows = "5"
|
|
||||||
solo
|
|
||||||
/>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn color = "primary" text @click = "importConf">确认</v-btn>
|
|
||||||
<v-btn text @click = "showShareDialog = false">取消</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
|
|
||||||
<v-card class="mb-4">
|
|
||||||
<v-subheader>订阅配置</v-subheader>
|
|
||||||
<v-form v-model="formState.basicValid" class="pl-4 pr-4 pb-0">
|
|
||||||
<v-text-field
|
|
||||||
v-model="options.name"
|
|
||||||
class="mt-2"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
label="订阅名称"
|
|
||||||
placeholder="填入订阅名称,名称需唯一"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<v-text-field
|
|
||||||
v-model="options['display-name']"
|
|
||||||
class="mt-2"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
label="订阅显示名称"
|
|
||||||
placeholder="填入订阅显示名称,名称无需唯一"
|
|
||||||
/>
|
|
||||||
<!--For Subscription-->
|
|
||||||
<v-radio-group
|
|
||||||
v-if="!isCollection"
|
|
||||||
v-model="options.source"
|
|
||||||
class="mt-0 mb-0"
|
|
||||||
>
|
|
||||||
<template v-slot:label>
|
|
||||||
<div>订阅来源</div>
|
|
||||||
</template>
|
|
||||||
<v-row dense>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="远程" value="remote" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="本地" value="local" />
|
|
||||||
</v-col>
|
|
||||||
<v-col></v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
<v-textarea
|
|
||||||
v-if="!isCollection && options.source !== 'local'"
|
|
||||||
v-model="options.url"
|
|
||||||
:rules="validations.urlRules"
|
|
||||||
auto-grow
|
|
||||||
class="mt-0"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
label="订阅链接"
|
|
||||||
placeholder="填入机场原始订阅链接"
|
|
||||||
required
|
|
||||||
rows="2"
|
|
||||||
/>
|
|
||||||
<v-textarea
|
|
||||||
v-if="options.source === 'local'"
|
|
||||||
v-model="options.content"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
label="订阅内容"
|
|
||||||
placeholder="填入原始订阅内容"
|
|
||||||
autogrow
|
|
||||||
rows="5"
|
|
||||||
row-height="15"
|
|
||||||
class="mt-0"
|
|
||||||
>
|
|
||||||
</v-textarea>
|
|
||||||
<v-textarea
|
|
||||||
v-if="!isCollection && options.source !== 'local'"
|
|
||||||
v-model="options.ua"
|
|
||||||
auto-grow
|
|
||||||
class="mt-2"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
label="User-Agent"
|
|
||||||
placeholder="自定义下载订阅使用的User-Agent,可选。"
|
|
||||||
rows="2"
|
|
||||||
/>
|
|
||||||
<!--For Collection-->
|
|
||||||
<v-list v-if="isCollection" dense>
|
|
||||||
<v-subheader class="pl-0">包含的订阅</v-subheader>
|
|
||||||
<v-list-item v-for="sub in availableSubs" :key="sub.name">
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-icon v-if="!sub.icon" color="teal darken-1">mdi-cloud</v-icon>
|
|
||||||
<v-img v-else :class="getIconClass(sub.icon)" :src="sub.icon" />
|
|
||||||
</v-list-item-avatar>
|
|
||||||
<v-list-item-content>
|
|
||||||
<template v-if="sub['display-name']">
|
|
||||||
{{ sub['display-name'] }} ({{ sub.name }})
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ sub.name }}
|
|
||||||
</template>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-checkbox v-model="selected" :value="sub.name" class="pr-1" />
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
<v-textarea
|
|
||||||
v-model="options.icon"
|
|
||||||
auto-grow
|
|
||||||
class="mt-2"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
label="图标链接"
|
|
||||||
placeholder="填入想要展示的图标链接,可选。"
|
|
||||||
rows="2"
|
|
||||||
/>
|
|
||||||
</v-form>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn text small @click = "save" class="fixedActionBtn">
|
|
||||||
<v-icon>mdi-content-save</v-icon>
|
|
||||||
保存
|
|
||||||
</v-btn>
|
|
||||||
<v-btn text small @click.stop = "showShareDialog = true" class = "fixedActionBtn">
|
|
||||||
<v-icon>mdi-file-import</v-icon>
|
|
||||||
导入
|
|
||||||
</v-btn>
|
|
||||||
<v-btn text small @click = "share" class = "fixedActionBtn">
|
|
||||||
<v-icon>mdi-share-circle</v-icon>
|
|
||||||
分享
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
<v-card class="mb-4">
|
|
||||||
<v-subheader>常用选项</v-subheader>
|
|
||||||
<v-form class="pl-4 pr-4">
|
|
||||||
<v-item-group>
|
|
||||||
<v-radio-group v-model="options.useless" class="mt-0 mb-0" dense>
|
|
||||||
过滤非法节点
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="保留" value="KEEP" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="删除" value="REMOVE" />
|
|
||||||
</v-col>
|
|
||||||
<v-col></v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
|
|
||||||
<v-radio-group v-model="options.udp" class="mt-0 mb-0" dense>
|
|
||||||
UDP转发
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="默认" value="DEFAULT" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制开启" value="FORCE_OPEN" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制关闭" value="FORCE_CLOSE" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
|
|
||||||
<v-radio-group
|
|
||||||
v-model="options['skip-cert-verify']"
|
|
||||||
class="mt-0 mb-0"
|
|
||||||
dense
|
|
||||||
>
|
|
||||||
跳过证书验证
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="默认" value="DEFAULT" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制跳过" value="FORCE_OPEN" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制验证" value="FORCE_CLOSE" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
<v-radio-group v-model="options.tfo" class="mt-0 mb-0" dense>
|
|
||||||
TCP Fast Open
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="默认" value="DEFAULT" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制开启" value="FORCE_OPEN" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制关闭" value="FORCE_CLOSE" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
<v-radio-group v-model="options['aead']" class="mt-0 mb-0" dense>
|
|
||||||
Vmess AEAD
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="默认" value="DEFAULT" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制开启" value="FORCE_OPEN" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制关闭" value="FORCE_CLOSE" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
</v-item-group>
|
|
||||||
</v-form>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<v-card class="mb-4">
|
|
||||||
<v-subheader>Surge 选项</v-subheader>
|
|
||||||
<v-form class="pl-4 pr-4">
|
|
||||||
<v-radio-group
|
|
||||||
v-model="options['surge-hybrid']"
|
|
||||||
class="mt-0 mb-0"
|
|
||||||
dense
|
|
||||||
>
|
|
||||||
Hybrid 策略
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="默认" value="DEFAULT" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制开启" value="FORCE_OPEN" />
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-radio label="强制关闭" value="FORCE_CLOSE" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-radio-group>
|
|
||||||
</v-form>
|
|
||||||
</v-card>
|
|
||||||
<v-card id="processors" class="mb-4">
|
|
||||||
<v-subheader> 节点操作
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon color = "primary" @click.stop = "dialog = true"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-plus-circle</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-subheader>
|
|
||||||
<!--<v-divider></v-divider>-->
|
|
||||||
<component
|
|
||||||
:is="p.component"
|
|
||||||
v-for="p in processors"
|
|
||||||
:key="p.id"
|
|
||||||
:args="p.args"
|
|
||||||
@dataChanged="dataChanged"
|
|
||||||
@deleteProcess="deleteProcess"
|
|
||||||
@down="moveDown"
|
|
||||||
@up="moveUp"
|
|
||||||
>
|
|
||||||
</component>
|
|
||||||
</v-card>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { showError, showInfo } from "@/utils";
|
|
||||||
import FloatMenu from "@/components/FloatMenu.vue";
|
|
||||||
import TypeFilter from "@/components/TypeFilter";
|
|
||||||
import RegionFilter from "@/components/RegionFilter";
|
|
||||||
import RegexFilter from "@/components/RegexFilter";
|
|
||||||
import SortOperator from "@/components/SortOperator";
|
|
||||||
import RegexRenameOperator from "@/components/RegexRenameOperator";
|
|
||||||
import RegexDeleteOperator from "@/components/RegexDeleteOperator";
|
|
||||||
import FlagOperator from "@/components/FlagOperator";
|
|
||||||
import ScriptFilter from "@/components/ScriptFilter";
|
|
||||||
import ScriptOperator from "@/components/ScriptOperator";
|
|
||||||
import RegexSortOperator from "@/components/RegexSortOperator";
|
|
||||||
import HandleDuplicateOperator from "@/components/HandleDuplicateOperator";
|
|
||||||
import ResolveDomainOperator from "@/components/ResolveDomainOperator";
|
|
||||||
|
|
||||||
const AVAILABLE_PROCESSORS = {
|
|
||||||
"Flag Operator": {
|
|
||||||
component: "FlagOperator",
|
|
||||||
name: "国旗",
|
|
||||||
},
|
|
||||||
"Type Filter": {
|
|
||||||
component: "TypeFilter",
|
|
||||||
name: "类型过滤器",
|
|
||||||
},
|
|
||||||
"Region Filter": {
|
|
||||||
component: "RegionFilter",
|
|
||||||
name: "区域过滤器",
|
|
||||||
},
|
|
||||||
"Regex Filter": {
|
|
||||||
component: "RegexFilter",
|
|
||||||
name: "正则过滤器",
|
|
||||||
},
|
|
||||||
"Sort Operator": {
|
|
||||||
component: "SortOperator",
|
|
||||||
name: "节点排序",
|
|
||||||
},
|
|
||||||
"Regex Sort Operator": {
|
|
||||||
component: "RegexSortOperator",
|
|
||||||
name: "正则排序",
|
|
||||||
},
|
|
||||||
"Regex Rename Operator": {
|
|
||||||
component: "RegexRenameOperator",
|
|
||||||
name: "正则重命名",
|
|
||||||
},
|
|
||||||
"Regex Delete Operator": {
|
|
||||||
component: "RegexDeleteOperator",
|
|
||||||
name: "删除正则",
|
|
||||||
},
|
|
||||||
"Handle Duplicate Operator": {
|
|
||||||
component: "HandleDuplicateOperator",
|
|
||||||
name: "节点去重",
|
|
||||||
},
|
|
||||||
"Resolve Domain Operator": {
|
|
||||||
component: "ResolveDomainOperator",
|
|
||||||
name: "节点域名解析",
|
|
||||||
},
|
|
||||||
"Script Filter": {
|
|
||||||
component: "ScriptFilter",
|
|
||||||
name: "脚本过滤器",
|
|
||||||
},
|
|
||||||
"Script Operator": {
|
|
||||||
component: "ScriptOperator",
|
|
||||||
name: "脚本操作",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
|
|
||||||
isCollection: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
FlagOperator,
|
|
||||||
RegexFilter,
|
|
||||||
RegionFilter,
|
|
||||||
TypeFilter,
|
|
||||||
SortOperator,
|
|
||||||
RegexSortOperator,
|
|
||||||
RegexRenameOperator,
|
|
||||||
RegexDeleteOperator,
|
|
||||||
ScriptFilter,
|
|
||||||
ScriptOperator,
|
|
||||||
HandleDuplicateOperator,
|
|
||||||
ResolveDomainOperator,
|
|
||||||
FloatMenu,
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
selectedProcess: null,
|
|
||||||
showShareDialog: false,
|
|
||||||
imported: "",
|
|
||||||
dialog: false,
|
|
||||||
validations: {
|
|
||||||
urlRules: [
|
|
||||||
(v) =>
|
|
||||||
this.options.source === "remote" && (!!v || "订阅链接不能为空!"),
|
|
||||||
(v) =>
|
|
||||||
this.options.source === "remote" &&
|
|
||||||
(/^https?:\/\//.test(v) || "订阅链接不合法!"),
|
|
||||||
],
|
|
||||||
importRules: [(v) => !!v || "不能导入空配置!"],
|
|
||||||
},
|
|
||||||
formState: {
|
|
||||||
basicValid: false,
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
name: "",
|
|
||||||
"display-name": "",
|
|
||||||
source: "",
|
|
||||||
url: "",
|
|
||||||
content: "",
|
|
||||||
icon: "",
|
|
||||||
ua: "",
|
|
||||||
useless: "KEEP",
|
|
||||||
udp: "DEFAULT",
|
|
||||||
"skip-cert-verify": "DEFAULT",
|
|
||||||
tfo: "DEFAULT",
|
|
||||||
"surge-hybrid": "DEFAULT",
|
|
||||||
aead: "DEFAULT",
|
|
||||||
},
|
|
||||||
process: [],
|
|
||||||
selected: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
const name = decodeURIComponent(this.$route.params.name);
|
|
||||||
let source;
|
|
||||||
if (this.isCollection) {
|
|
||||||
source =
|
|
||||||
typeof name === "undefined" || name === "UNTITLED"
|
|
||||||
? {}
|
|
||||||
: this.$store.state.collections[name];
|
|
||||||
this.$store.commit(
|
|
||||||
"SET_NAV_TITLE",
|
|
||||||
source.name ? `组合订阅编辑 ➤ ${source.name}` : "新建组合订阅"
|
|
||||||
);
|
|
||||||
this.selected = source.subscriptions || [];
|
|
||||||
} else {
|
|
||||||
source =
|
|
||||||
typeof name === "undefined" || name === "UNTITLED"
|
|
||||||
? {}
|
|
||||||
: this.$store.state.subscriptions[name];
|
|
||||||
this.$store.commit(
|
|
||||||
"SET_NAV_TITLE",
|
|
||||||
source.name ? `订阅编辑 ➤ ${source.name}` : "新建订阅"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.name = source.name;
|
|
||||||
const { options, process } = loadProcess(this.options, source);
|
|
||||||
this.options = options;
|
|
||||||
this.process = process;
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
availableSubs() {
|
|
||||||
return this.$store.state.subscriptions;
|
|
||||||
},
|
|
||||||
|
|
||||||
availableProcessors() {
|
|
||||||
return AVAILABLE_PROCESSORS;
|
|
||||||
},
|
|
||||||
|
|
||||||
processors() {
|
|
||||||
return this.process
|
|
||||||
.filter((p) => AVAILABLE_PROCESSORS[p.type])
|
|
||||||
.map((p) => {
|
|
||||||
return {
|
|
||||||
component: AVAILABLE_PROCESSORS[p.type].component,
|
|
||||||
args: p.args,
|
|
||||||
id: p.id,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
config() {
|
|
||||||
const output = {
|
|
||||||
name: this.options.name,
|
|
||||||
"display-name": this.options["display-name"],
|
|
||||||
icon: this.options.icon,
|
|
||||||
process: [],
|
|
||||||
};
|
|
||||||
if (this.isCollection) {
|
|
||||||
output.subscriptions = this.selected;
|
|
||||||
} else {
|
|
||||||
output.url = this.options.url;
|
|
||||||
output.source = this.options.source;
|
|
||||||
output.content = this.options.content;
|
|
||||||
}
|
|
||||||
// assign user-agent, if ua is set
|
|
||||||
let ua = this.options.ua;
|
|
||||||
if (typeof ua != "undefined" && ua != null && ua.trim().length > 0) {
|
|
||||||
output.ua = ua;
|
|
||||||
} else {
|
|
||||||
output.ua = "";
|
|
||||||
}
|
|
||||||
// useless filter
|
|
||||||
if (this.options.useless === "REMOVE") {
|
|
||||||
output.process.push({
|
|
||||||
type: "Useless Filter",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// udp, tfo, scert, surge-hybrid, aead
|
|
||||||
for (const opt of [
|
|
||||||
"udp",
|
|
||||||
"tfo",
|
|
||||||
"skip-cert-verify",
|
|
||||||
"surge-hybrid",
|
|
||||||
"aead",
|
|
||||||
]) {
|
|
||||||
if (this.options[opt] !== "DEFAULT") {
|
|
||||||
output.process.push({
|
|
||||||
type: "Set Property Operator",
|
|
||||||
args: { key: opt, value: this.options[opt] === "FORCE_OPEN" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const p of this.process) {
|
|
||||||
output.process.push(p);
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getIconClass(url) {
|
|
||||||
return url.indexOf("#invert") !== -1 && !this.$vuetify.theme.dark
|
|
||||||
? "invert"
|
|
||||||
: "";
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
if (this.isCollection) {
|
|
||||||
if (this.options.name && this.selected) {
|
|
||||||
if (this.$route.params.name === "UNTITLED") {
|
|
||||||
this.$store
|
|
||||||
.dispatch("NEW_COLLECTION", this.config)
|
|
||||||
.then(() => {
|
|
||||||
showInfo(`成功创建组合订阅:${this.name}!`);
|
|
||||||
this.$router.back();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showError(`发生错误,无法创建组合订阅!`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$store
|
|
||||||
.dispatch("UPDATE_COLLECTION", {
|
|
||||||
name: this.$route.params.name,
|
|
||||||
collection: this.config,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
showInfo(`成功保存组合订阅:${this.name}!`);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showError(`发生错误,无法保存组合订阅!`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("Saving subscription...");
|
|
||||||
if (!this.options.name) {
|
|
||||||
showError(`订阅名字不能为空!`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.options.source === "remote" && !this.options.url) {
|
|
||||||
showError(`订阅链接不能为空!`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.$route.params.name !== "UNTITLED") {
|
|
||||||
this.$store
|
|
||||||
.dispatch("UPDATE_SUBSCRIPTION", {
|
|
||||||
name: this.$route.params.name,
|
|
||||||
sub: this.config,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
showInfo(`成功保存订阅:${this.options.name}!`);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showError(`发生错误,无法保存订阅!`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$store
|
|
||||||
.dispatch("NEW_SUBSCRIPTION", this.config)
|
|
||||||
.then(() => {
|
|
||||||
showInfo(`成功创建订阅:${this.options.name}!`);
|
|
||||||
this.$router.back();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showError(`发生错误,无法创建订阅!`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
share() {
|
|
||||||
let config = this.config;
|
|
||||||
config.name = "「订阅名称」";
|
|
||||||
if (this.isCollection) {
|
|
||||||
config.subscriptions = [];
|
|
||||||
} else {
|
|
||||||
config.url = "「订阅链接」";
|
|
||||||
}
|
|
||||||
config = JSON.stringify(config);
|
|
||||||
this.$clipboard(config);
|
|
||||||
this.$store.commit(
|
|
||||||
"SET_SUCCESS_MESSAGE",
|
|
||||||
"导出成功,订阅已复制到剪贴板!"
|
|
||||||
);
|
|
||||||
// this.showShareDialog = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
importConf() {
|
|
||||||
if (this.imported) {
|
|
||||||
const sub = JSON.parse(this.imported);
|
|
||||||
const { options, process } = loadProcess(this.options, sub);
|
|
||||||
delete options.name;
|
|
||||||
delete options.url;
|
|
||||||
delete options.content;
|
|
||||||
|
|
||||||
Object.assign(this.options, options);
|
|
||||||
this.process = process;
|
|
||||||
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", "成功导入订阅!");
|
|
||||||
this.showShareDialog = false;
|
|
||||||
} else {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", "不能导入空配置!");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
dataChanged(content) {
|
|
||||||
let index = 0;
|
|
||||||
for (; index < this.process.length; index++) {
|
|
||||||
if (this.process[index].id === content.idx) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.process[index].args = content.args;
|
|
||||||
},
|
|
||||||
|
|
||||||
addProcess(type) {
|
|
||||||
this.process.push({ type, id: uuidv4() });
|
|
||||||
this.dialog = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteProcess(id) {
|
|
||||||
let index = 0;
|
|
||||||
for (; index < this.process.length; index++) {
|
|
||||||
if (this.process[index].id === id) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.process.splice(index, 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
moveUp(id) {
|
|
||||||
let index = 0;
|
|
||||||
for (; index < this.process.length; index++) {
|
|
||||||
if (this.process[index].id === id) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index === 0) return;
|
|
||||||
// otherwise swap with previous one
|
|
||||||
const prev = this.process[index - 1];
|
|
||||||
const cur = this.process[index];
|
|
||||||
this.process.splice(index - 1, 2, cur, prev);
|
|
||||||
},
|
|
||||||
|
|
||||||
moveDown(id) {
|
|
||||||
let index = 0;
|
|
||||||
for (; index < this.process.length; index++) {
|
|
||||||
if (this.process[index].id === id) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// otherwise swap with latter one
|
|
||||||
const cur = this.process[index];
|
|
||||||
const next = this.process[index + 1];
|
|
||||||
this.process.splice(index, 2, next, cur);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function loadProcess(options, source, isCollection = false) {
|
|
||||||
options = {
|
|
||||||
...options,
|
|
||||||
name: source.name,
|
|
||||||
"display-name": source["display-name"],
|
|
||||||
icon: source.icon,
|
|
||||||
ua: source.ua,
|
|
||||||
};
|
|
||||||
if (isCollection) {
|
|
||||||
options.subscriptions = source.subscriptions;
|
|
||||||
} else {
|
|
||||||
options.url = source.url;
|
|
||||||
options.source = source.source || "remote";
|
|
||||||
options.content = source.content;
|
|
||||||
}
|
|
||||||
let process = [];
|
|
||||||
|
|
||||||
// flag
|
|
||||||
for (const p of source.process || []) {
|
|
||||||
switch (p.type) {
|
|
||||||
case "Useless Filter":
|
|
||||||
options.useless = "REMOVE";
|
|
||||||
break;
|
|
||||||
case "Set Property Operator":
|
|
||||||
options[p.args.key] = p.args.value ? "FORCE_OPEN" : "FORCE_CLOSE";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
p.id = uuidv4();
|
|
||||||
process.push(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { options, process };
|
|
||||||
}
|
|
||||||
|
|
||||||
function uuidv4() {
|
|
||||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
|
||||||
var r = (Math.random() * 16) | 0,
|
|
||||||
v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
||||||
return v.toString(16);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.invert {
|
|
||||||
filter: invert(100%);
|
|
||||||
}
|
|
||||||
.fixedActionBtn{
|
|
||||||
&.theme--dark{
|
|
||||||
color: #ffffffcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.theme--light {
|
|
||||||
color : #00000099;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,371 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container fluid>
|
|
||||||
<v-dialog v-model = "showPreviewDialog" scrollable>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>预览转换结果</v-card-title>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-list flat>
|
|
||||||
<v-list-item v-for = "platform in platformList" :key = "platform.name"
|
|
||||||
@click = "previewSpecificPlatform(platform.path)"
|
|
||||||
>
|
|
||||||
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-img :class = "getIconClass('#invert')" :src = "platform.icon"
|
|
||||||
/>
|
|
||||||
</v-list-item-avatar>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title v-text = "platform.name"></v-list-item-title>
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn text @click = "showPreviewDialog = false">取消</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon left>local_airport</v-icon>
|
|
||||||
单个订阅
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click = "createSub">
|
|
||||||
<v-icon color = "primary">mdi-plus-circle</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-item v-for = "sub in subscriptions" :key = "sub.name"
|
|
||||||
@click = "preview(sub)"
|
|
||||||
>
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-icon v-if = "!sub.icon" color = "teal darken-1">mdi-cloud
|
|
||||||
</v-icon>
|
|
||||||
<v-img v-else :class = "getIconClass(sub.icon)"
|
|
||||||
:src = "sub.icon"
|
|
||||||
/>
|
|
||||||
</v-list-item-avatar>
|
|
||||||
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title class = "font-weight-medium"
|
|
||||||
v-text = "sub['display-name'] || sub.name"
|
|
||||||
></v-list-item-title>
|
|
||||||
<v-list-item-title v-text = "sub.url"></v-list-item-title>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-list-item-action>
|
|
||||||
<v-menu bottom left>
|
|
||||||
<template v-slot:activator = "{ on, attrs }">
|
|
||||||
<v-btn v-bind = "attrs" v-on = "on" icon>
|
|
||||||
<v-icon>mdi-dots-vertical</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<v-list>
|
|
||||||
<v-list-item v-for = "(menuItem, i) in editMenu" :key = "i"
|
|
||||||
@click = "subscriptionMenu(menuItem.action, sub)"
|
|
||||||
>
|
|
||||||
<v-list-item-content>{{
|
|
||||||
menuItem.title
|
|
||||||
}}
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</v-list-item-action>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon left>work_outline</v-icon>
|
|
||||||
组合订阅
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click = "createCol">
|
|
||||||
<v-icon color = "primary">mdi-plus-circle</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-item v-for = "collection in collections"
|
|
||||||
:key = "collection.name" dense
|
|
||||||
@click = "preview(collection, type='collection')"
|
|
||||||
>
|
|
||||||
<v-list-item-avatar>
|
|
||||||
<v-icon v-if = "!collection.icon" color = "teal darken-1">
|
|
||||||
mdi-cloud
|
|
||||||
</v-icon>
|
|
||||||
<v-img v-else :class = "getIconClass(collection.icon)"
|
|
||||||
:src = "collection.icon"
|
|
||||||
/>
|
|
||||||
</v-list-item-avatar>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title class = "font-weight-medium"
|
|
||||||
v-text = "collection['display-name'] || collection.name"
|
|
||||||
></v-list-item-title>
|
|
||||||
<v-chip-group column>
|
|
||||||
<v-chip v-for = "subs in collection.subsInfo" :key = "subs.name"
|
|
||||||
class = "ma-2 ml-0 mr-1 pa-2" label small
|
|
||||||
>
|
|
||||||
{{ subs['display-name'] || subs.name }}
|
|
||||||
</v-chip>
|
|
||||||
</v-chip-group>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-list-item-action>
|
|
||||||
<v-menu bottom left>
|
|
||||||
<template v-slot:activator = "{ on, attrs }">
|
|
||||||
<v-btn v-bind = "attrs" v-on = "on" icon>
|
|
||||||
<v-icon>mdi-dots-vertical</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<v-list>
|
|
||||||
<v-list-item v-for = "(menuItem, i) in editMenu" :key = "i"
|
|
||||||
@click = "collectionMenu(menuItem.action, collection)"
|
|
||||||
>
|
|
||||||
<v-list-item-content>{{
|
|
||||||
menuItem.title
|
|
||||||
}}
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</v-list-item-action>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
<v-dialog v-model = "showProxyList" fullscreen hide-overlay scrollable
|
|
||||||
transition = "dialog-bottom-transition"
|
|
||||||
>
|
|
||||||
<v-card fluid>
|
|
||||||
<v-toolbar class = "flex-grow-0">
|
|
||||||
<v-icon>mdi-dns</v-icon>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-toolbar-title>
|
|
||||||
<h4>节点列表</h4>
|
|
||||||
</v-toolbar-title>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-toolbar-items>
|
|
||||||
<v-btn icon @click = "showProxyList = false">
|
|
||||||
<v-icon>mdi-close</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-toolbar-items>
|
|
||||||
<template v-slot:extension>
|
|
||||||
<v-tabs v-model = "tab" centered grow>
|
|
||||||
<v-tabs-slider color = "primary" />
|
|
||||||
<v-tab key = "raw">
|
|
||||||
<h4>原始节点</h4>
|
|
||||||
</v-tab>
|
|
||||||
<v-tab key = "processed">
|
|
||||||
<h4>生成节点</h4>
|
|
||||||
</v-tab>
|
|
||||||
</v-tabs>
|
|
||||||
</template>
|
|
||||||
</v-toolbar>
|
|
||||||
<v-card-text>
|
|
||||||
<v-tabs-items v-model = "tab">
|
|
||||||
<v-tab-item key = "raw">
|
|
||||||
<proxy-list :key = "url + 'raw'" ref = "proxyList" :raw = "true"
|
|
||||||
:sub = "sub" :url = "url"
|
|
||||||
></proxy-list>
|
|
||||||
</v-tab-item>
|
|
||||||
<v-tab-item key = "processed">
|
|
||||||
<proxy-list :key = "url" ref = "proxyList" :sub = "sub"
|
|
||||||
:url = "url"
|
|
||||||
></proxy-list>
|
|
||||||
</v-tab-item>
|
|
||||||
</v-tabs-items>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ProxyList from '@/components/ProxyList'
|
|
||||||
import { BACKEND_BASE } from '@/config'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components : { ProxyList },
|
|
||||||
data : () => {
|
|
||||||
return {
|
|
||||||
opened : false,
|
|
||||||
showProxyList : false,
|
|
||||||
showPreviewDialog : false,
|
|
||||||
previewSubName : '',
|
|
||||||
isCollectionPreview : false,
|
|
||||||
url : '',
|
|
||||||
sub : [],
|
|
||||||
tab : 1,
|
|
||||||
platformList : [
|
|
||||||
{
|
|
||||||
name : 'Clash',
|
|
||||||
path : 'Clash',
|
|
||||||
icon : 'https://raw.githubusercontent.com/58xinian/icon/master/clash_mini.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'Quantumult X',
|
|
||||||
path : 'QX',
|
|
||||||
icon : 'https://raw.githubusercontent.com/Orz-3/mini/none/quanX.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'Surge',
|
|
||||||
path : 'Surge',
|
|
||||||
icon : 'https://raw.githubusercontent.com/Orz-3/mini/none/surge.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'Loon',
|
|
||||||
path : 'Loon',
|
|
||||||
icon : 'https://raw.githubusercontent.com/Orz-3/mini/none/loon.png',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name : 'Stash',
|
|
||||||
path : 'Stash',
|
|
||||||
icon : 'https://raw.githubusercontent.com/Orz-3/mini/master/Alpha/stash.png',
|
|
||||||
}
|
|
||||||
|
|
||||||
],
|
|
||||||
editMenu : [
|
|
||||||
{
|
|
||||||
title : '链接',
|
|
||||||
action : 'COPY'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title : '编辑',
|
|
||||||
action : 'EDIT'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title : '预览',
|
|
||||||
action : 'PREVIEW'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title : '删除',
|
|
||||||
action : 'DELETE'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed : {
|
|
||||||
subscriptionBaseURL (){
|
|
||||||
return BACKEND_BASE
|
|
||||||
},
|
|
||||||
subscriptions : {
|
|
||||||
get (){
|
|
||||||
const subs = this.$store.state.subscriptions
|
|
||||||
return Object.keys(subs).map(k => subs[k])
|
|
||||||
},
|
|
||||||
set (){
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
collections (){
|
|
||||||
const cols = this.$store.state.collections
|
|
||||||
const collections = Object.keys(cols).map(k => cols[k])
|
|
||||||
const subscriptions = this.$store.state.subscriptions
|
|
||||||
collections.map(item => {
|
|
||||||
item.subsInfo = []
|
|
||||||
item.subscriptions.map(sub => item.subsInfo.push(subscriptions[sub]))
|
|
||||||
})
|
|
||||||
return collections
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods : {
|
|
||||||
previewSpecificPlatform (path){
|
|
||||||
window.open(`${this.subscriptionBaseURL}/download/${this.isCollectionPreview ? 'collection/' : ''}${this.previewSubName}?target=${path}`)
|
|
||||||
this.showPreviewDialog = false
|
|
||||||
},
|
|
||||||
subscriptionMenu (action, sub){
|
|
||||||
console.log(`${action} --> ${sub.name}`)
|
|
||||||
switch (action){
|
|
||||||
case 'COPY':
|
|
||||||
this.$clipboard(
|
|
||||||
`${this.subscriptionBaseURL}/download/${encodeURIComponent(
|
|
||||||
sub.name)}`)
|
|
||||||
this.$store.commit('SET_SUCCESS_MESSAGE', '成功复制订阅链接')
|
|
||||||
break
|
|
||||||
case 'EDIT':
|
|
||||||
this.$router.push(`/sub-edit/${encodeURIComponent(sub.name)}`)
|
|
||||||
break
|
|
||||||
case 'PREVIEW':
|
|
||||||
this.previewSubName = sub.name
|
|
||||||
this.isCollectionPreview = false
|
|
||||||
this.showPreviewDialog = true
|
|
||||||
break
|
|
||||||
case 'DELETE':
|
|
||||||
this.$store.dispatch(
|
|
||||||
'DELETE_SUBSCRIPTION', encodeURIComponent(sub.name))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
collectionMenu (action, collection){
|
|
||||||
console.log(`${action} --> ${collection.name}`)
|
|
||||||
switch (action){
|
|
||||||
case 'COPY':
|
|
||||||
this.$clipboard(
|
|
||||||
`${this.subscriptionBaseURL}/download/collection/${encodeURIComponent(
|
|
||||||
collection.name)}`)
|
|
||||||
this.$store.commit('SET_SUCCESS_MESSAGE', '成功复制订阅链接')
|
|
||||||
break
|
|
||||||
case 'EDIT':
|
|
||||||
this.$router.push(`/collection-edit/${collection.name}`)
|
|
||||||
break
|
|
||||||
case 'PREVIEW':
|
|
||||||
this.previewSubName = collection.name
|
|
||||||
this.isCollectionPreview = true
|
|
||||||
this.showPreviewDialog = true
|
|
||||||
break
|
|
||||||
case 'DELETE':
|
|
||||||
this.$store.dispatch('DELETE_COLLECTION', collection.name)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
preview (item, type = 'sub'){
|
|
||||||
if (type === 'sub'){
|
|
||||||
this.url = `${BACKEND_BASE}/download/${encodeURIComponent(
|
|
||||||
item.name)}`
|
|
||||||
this.sub = item.url
|
|
||||||
} else{
|
|
||||||
this.url = `${BACKEND_BASE}/download/collection/${encodeURIComponent(
|
|
||||||
item.name)}`
|
|
||||||
}
|
|
||||||
this.showProxyList = true
|
|
||||||
},
|
|
||||||
createSub (){
|
|
||||||
this.$router.push('/sub-edit/UNTITLED')
|
|
||||||
},
|
|
||||||
createCol (){
|
|
||||||
this.$router.push('/collection-edit/UNTITLED')
|
|
||||||
},
|
|
||||||
async refreshProxyList (){
|
|
||||||
try{
|
|
||||||
await this.$refs.proxyList.refresh()
|
|
||||||
this.$store.commit('SET_SUCCESS_MESSAGE', '刷新成功!')
|
|
||||||
} catch (err){
|
|
||||||
this.$store.commit('SET_ERROR_MESSAGE', err.response.data.message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getIconClass (url){
|
|
||||||
return url.indexOf(
|
|
||||||
'#invert') !== - 1 && !this.$vuetify.theme.dark ? 'invert' : ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.invert {
|
|
||||||
filter : invert(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-dialog > .v-card > .v-toolbar {
|
|
||||||
position : sticky;
|
|
||||||
top : 0;
|
|
||||||
z-index : 999;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,170 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container fluid>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon left>mdi-cloud</v-icon>
|
|
||||||
数据同步
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon @click="openGist()">
|
|
||||||
<v-icon color="primary" small>visibility</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
最近同步于:{{ syncTime }}
|
|
||||||
</v-card-text>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn :loading="status.uploading"
|
|
||||||
class="ma-2 white--text"
|
|
||||||
color="blue-grey"
|
|
||||||
small
|
|
||||||
@click="sync('upload')">
|
|
||||||
上传
|
|
||||||
<v-icon right>
|
|
||||||
mdi-cloud-upload
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn :loading="status.downloading"
|
|
||||||
class="ma-2 white--text"
|
|
||||||
color="blue-grey"
|
|
||||||
small
|
|
||||||
@click="sync('download')">
|
|
||||||
恢复
|
|
||||||
<v-icon right>
|
|
||||||
mdi-cloud-download
|
|
||||||
</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>
|
|
||||||
<v-icon left>settings</v-icon>
|
|
||||||
设置
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<v-list dense>
|
|
||||||
<v-subheader>GitHub配置</v-subheader>
|
|
||||||
<v-list-item>
|
|
||||||
<v-col>
|
|
||||||
<v-row>
|
|
||||||
<v-text-field
|
|
||||||
v-model="settings.githubUser"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
hint="填入GitHub用户名" label="GitHub 用户名"
|
|
||||||
/>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-text-field
|
|
||||||
v-model="settings.gistToken"
|
|
||||||
clear-icon="clear"
|
|
||||||
clearable
|
|
||||||
hint="填入GitHub Token" label="GitHub Token"
|
|
||||||
/>
|
|
||||||
</v-row>
|
|
||||||
</v-col>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card-text>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn color="primary" small text @click="save()">保存</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {axios, showError} from "@/utils";
|
|
||||||
import {format} from "timeago.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
status: {
|
|
||||||
uploading: false,
|
|
||||||
downloading: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
settings: {
|
|
||||||
get() {
|
|
||||||
return this.$store.state.settings;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$store.state.settings = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
syncTime() {
|
|
||||||
if (this.settings.syncTime) {
|
|
||||||
return format(this.settings.syncTime, "zh_CN");
|
|
||||||
} else {
|
|
||||||
return "从未同步";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async save() {
|
|
||||||
await axios.patch(`/settings`, this.settings);
|
|
||||||
await this.$store.dispatch("FETCH_SETTINGS");
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", `保存成功!`);
|
|
||||||
},
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
async sync(action) {
|
|
||||||
const setLoading = (status) => {
|
|
||||||
if (action === 'upload') {
|
|
||||||
this.status.uploading = status;
|
|
||||||
} else if (action === 'download') {
|
|
||||||
this.status.downloading = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.settings.gistToken) {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", "未设置GitHub Token!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
axios.get(`/utils/backup?action=${action}`).then(resp => {
|
|
||||||
if (resp.data.status === 'success') {
|
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", `${action === 'upload' ? "备份" : "还原"}成功!`);
|
|
||||||
this.updateStore(this.$store);
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", `备份失败!${err}`);
|
|
||||||
}).finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateStore(store) {
|
|
||||||
store.dispatch('FETCH_SUBSCRIPTIONS').catch(() => {
|
|
||||||
showError(`无法拉取订阅列表!`);
|
|
||||||
});
|
|
||||||
store.dispatch("FETCH_COLLECTIONS").catch(() => {
|
|
||||||
showError(`无法拉取组合订阅列表!`);
|
|
||||||
});
|
|
||||||
store.dispatch("FETCH_SETTINGS").catch(() => {
|
|
||||||
showError(`无法拉取设置!`);
|
|
||||||
});
|
|
||||||
store.dispatch("FETCH_ARTIFACTS").catch(() => {
|
|
||||||
showError(`无法拉取同步配置!`);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
openGist() {
|
|
||||||
window.open(`https://gist.github.com${'/' + this.settings.githubUser || ''}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "sub-store",
|
|
||||||
"version": 2,
|
|
||||||
"builds": [
|
|
||||||
{
|
|
||||||
"src": "package.json",
|
|
||||||
"use": "@vercel/static-build"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"src": "/(js|css|img)/.*",
|
|
||||||
"headers": { "cache-control": "max-age=31536000, immutable" }
|
|
||||||
},
|
|
||||||
{ "handle": "filesystem" },
|
|
||||||
{ "src": ".*", "dest": "/" }
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"transpileDependencies": [
|
|
||||||
"vuetify",
|
|
||||||
'vue-echarts',
|
|
||||||
'resize-detector'
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user