mirror of
				https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
				synced 2025-10-31 08:51:04 +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
	 Peng-YM
						Peng-YM