From 0a11261eee9c943ada30e666d20193d7ec55c6cc Mon Sep 17 00:00:00 2001
From: Peng-YM <1048217874pengym@gmail.com>
Date: Sun, 23 Aug 2020 00:56:35 +0800
Subject: [PATCH] Added subscription page
---
backend/.idea/workspace.xml | 116 +++++++++++++++++++++++++++--
backend/sub-store.js | 43 ++++++++---
web/package-lock.json | 39 +++-------
web/package.json | 5 +-
web/src/App.vue | 16 ++--
web/src/components/BottomNav.vue | 6 +-
web/src/components/TopToolbar.vue | 8 +-
web/src/main.js | 2 +
web/src/router/index.js | 17 ++++-
web/src/store/index.js | 56 ++++++++++++++
web/src/utils/index.js | 10 +++
web/src/views/CollectionEditor.vue | 13 ++++
web/src/views/Dashboard.vue | 1 -
web/src/views/PopUpProxyList.vue | 13 ++++
web/src/views/SubEditor.vue | 13 ++++
web/src/views/Subscription.vue | 107 +++++++++++++++++++++++++-
web/src/views/User.vue | 1 -
17 files changed, 401 insertions(+), 65 deletions(-)
create mode 100644 web/src/store/index.js
create mode 100644 web/src/utils/index.js
create mode 100644 web/src/views/CollectionEditor.vue
create mode 100644 web/src/views/PopUpProxyList.vue
create mode 100644 web/src/views/SubEditor.vue
diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml
index d5f5bf9..c41746a 100644
--- a/backend/.idea/workspace.xml
+++ b/backend/.idea/workspace.xml
@@ -20,20 +20,23 @@
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
@@ -55,6 +58,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -66,7 +85,8 @@
1597827738046
-
+
+
@@ -85,4 +105,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/sub-store.js b/backend/sub-store.js
index 3e65322..c8697b7 100644
--- a/backend/sub-store.js
+++ b/backend/sub-store.js
@@ -21,12 +21,12 @@ if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
// BACKEND API
const $app = express();
-$app.route("/sub/:name")
+$app.route("/api/sub/:name")
.get(getSub)
.patch(updateSub)
.delete(deleteSub);
-$app.route("/sub")
+$app.route("/api/sub")
.get(getAllSubs)
.post(newSub)
.delete(deleteAllSubs);
@@ -36,11 +36,11 @@ $app.get("/download/:name", downloadSub);
// collections
$app.get("/download/collection/:name", downloadCollection);
-$app.route("/collection/:name")
+$app.route("/api/collection/:name")
.get(getCollection)
.patch(updateCollection)
.delete(deleteCollection);
-$app.route("/collection")
+$app.route("/api/collection")
.get(getAllCollections)
.post(newCollection)
.delete(deleteAllCollections);
@@ -203,8 +203,21 @@ async function updateSub(req, res) {
...sub
};
// allow users to update the subscription name
- delete allSubs[name];
- allSubs[sub.name || name] = newSub;
+ if (name !== sub.name) {
+ // we need to find out all collections refer to this name
+ const allCols = $.read(COLLECTIONS_KEY);
+ for (const k of Object.keys(allCols)) {
+ const idx = allCols[k].subscriptions.indexOf(name);
+ if (idx !== -1) {
+ allCols[k].subscriptions[idx] = sub.name;
+ }
+ }
+ // update subscriptions
+ delete allSubs[name];
+ allSubs[sub.name] = newSub;
+ } else {
+ allSubs[name] = newSub;
+ }
$.write(allSubs, SUBS_KEY);
res.json({
status: "success",
@@ -232,7 +245,7 @@ async function getAllSubs(req, res) {
const allSubs = $.read(SUBS_KEY);
res.json({
status: "success",
- data: Object.keys(allSubs)
+ data: allSubs
});
}
@@ -349,7 +362,7 @@ async function getAllCollections(req, res) {
const allCols = $.read(COLLECTIONS_KEY);
res.json({
status: "success",
- data: Object.keys(allCols)
+ data: allCols
});
}
@@ -2059,6 +2072,12 @@ function API(name = "untitled", debug = false) {
/*********************************** Mini Express *************************************/
function express(port = 3000) {
const {isNode} = ENV();
+ const DEFAULT_HEADERS = {
+ "Content-Type": "text/plain;charset=UTF-8",
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"
+ };
+
// node support
if (isNode) {
@@ -2068,6 +2087,10 @@ function express(port = 3000) {
app.use(bodyParser.json({verify: rawBodySaver}));
app.use(bodyParser.urlencoded({verify: rawBodySaver, extended: true}));
app.use(bodyParser.raw({verify: rawBodySaver, type: '*/*'}));
+ app.use((req, res, next) => {
+ res.set(DEFAULT_HEADERS);
+ next();
+ })
// adapter
app.start = () => {
@@ -2184,9 +2207,7 @@ function express(port = 3000) {
function Response() {
let statusCode = "200";
const {isQX, isLoon, isSurge} = ENV();
- const headers = {
- "Content-Type": "text/plain;charset=UTF-8",
- };
+ const headers = DEFAULT_HEADERS;
return new (class {
status(code) {
statusCode = code;
diff --git a/web/package-lock.json b/web/package-lock.json
index a4f62b2..a6f678c 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -2485,34 +2485,11 @@
"dev": true
},
"axios": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
- "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
+ "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
"requires": {
- "follow-redirects": "1.5.10"
- },
- "dependencies": {
- "debug": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "follow-redirects": {
- "version": "1.5.10",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
- "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
- "requires": {
- "debug": "=3.1.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- }
+ "follow-redirects": "^1.10.0"
}
},
"babel-eslint": {
@@ -5496,8 +5473,7 @@
"follow-redirects": {
"version": "1.13.0",
"resolved": "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.13.0.tgz?cache=0&sync_timestamp=1597057976909&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.13.0.tgz",
- "integrity": "sha1-tC6Nk6Kn7qXtiGM2dtZZe8jjhNs=",
- "dev": true
+ "integrity": "sha1-tC6Nk6Kn7qXtiGM2dtZZe8jjhNs="
},
"for-in": {
"version": "1.0.2",
@@ -11168,6 +11144,11 @@
"loader-utils": "^1.2.0"
}
},
+ "vuex": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
+ "integrity": "sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw=="
+ },
"watchpack": {
"version": "1.7.4",
"resolved": "https://registry.npm.taobao.org/watchpack/download/watchpack-1.7.4.tgz?cache=0&sync_timestamp=1597081659128&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwatchpack%2Fdownload%2Fwatchpack-1.7.4.tgz",
diff --git a/web/package.json b/web/package.json
index 357116d..a3ff5bf 100644
--- a/web/package.json
+++ b/web/package.json
@@ -8,13 +8,14 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
- "axios": "^0.19.2",
+ "axios": "^0.20.0",
"core-js": "^3.6.5",
"lodash": "^4.17.20",
"material-design-icons-iconfont": "^5.0.1",
"vue": "^2.6.11",
"vue-router": "^3.4.3",
- "vuetify": "^2.2.11"
+ "vuetify": "^2.2.11",
+ "vuex": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
diff --git a/web/src/App.vue b/web/src/App.vue
index 73f9289..62321b8 100644
--- a/web/src/App.vue
+++ b/web/src/App.vue
@@ -12,6 +12,11 @@
import TopToolbar from "@/components/TopToolbar";
import BottomNav from "@/components/BottomNav";
+function initStore(store) {
+ store.dispatch('FETCH_SUBSCRIPTIONS');
+ store.dispatch("FETCH_COLLECTIONS");
+}
+
export default {
components: {
TopToolbar,
@@ -19,14 +24,15 @@ export default {
},
created() {
- this.$vuetify.theme.dark = true;
- this.$vuetify.theme.themes.dark.primary = '#d02f2f';
+ this.$vuetify.theme.dark = this.$store.state.isDarkMode;
+ this.$vuetify.theme.themes.dark.primary = '#ae51e3';
+ this.$vuetify.theme.themes.light.primary = '#d73964';
+
+ initStore(this.$store);
},
computed: {
- isDarkMode() {
- return true;
- }
+
}
}
\ No newline at end of file
diff --git a/web/src/components/BottomNav.vue b/web/src/components/BottomNav.vue
index efb79d0..12a8333 100644
--- a/web/src/components/BottomNav.vue
+++ b/web/src/components/BottomNav.vue
@@ -9,17 +9,17 @@
>
首页
- dashboard
+ speed
订阅
- favorite
+ mdi-cloud
我的
- settings
+ mdi-account
diff --git a/web/src/components/TopToolbar.vue b/web/src/components/TopToolbar.vue
index 8d5909e..ea08500 100644
--- a/web/src/components/TopToolbar.vue
+++ b/web/src/components/TopToolbar.vue
@@ -34,7 +34,7 @@
>
- SubStore
+ {{title}}
@@ -56,6 +56,12 @@ export default {
doNothing: function () {
}
+ },
+
+ computed: {
+ title: function () {
+ return this.$store.state.title;
+ }
}
}
\ No newline at end of file
diff --git a/web/src/main.js b/web/src/main.js
index 5a1ea5e..d87a524 100644
--- a/web/src/main.js
+++ b/web/src/main.js
@@ -2,11 +2,13 @@ import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
import router from './router';
+import store from './store';
Vue.config.productionTip = false
new Vue({
vuetify,
router,
+ store,
render: h => h(App)
}).$mount('#app')
diff --git a/web/src/router/index.js b/web/src/router/index.js
index ddd8bb9..4ca156a 100644
--- a/web/src/router/index.js
+++ b/web/src/router/index.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import Router from 'vue-router';
+import store from "../store";
import Subscription from "@/views/Subscription";
import Dashboard from "@/views/Dashboard";
@@ -14,19 +15,29 @@ const router = new Router({
{
path: "/",
name: "subscriptions",
- component: Subscription
+ component: Subscription,
+ meta: {title: "订阅"}
},
{
path: "/dashboard",
name: "dashboard",
- component: Dashboard
+ component: Dashboard,
+ meta: {title: "首页"}
},
{
path: "/user",
name: "user",
- component: User
+ component: User,
+ meta: {title: "我的"}
}
]
});
+router.beforeEach((to, from, next) => {
+ const {meta} = to;
+ document.title = to.meta.title
+ store.commit("SET_NAV_TITLE", meta.title);
+ next();
+})
+
export default router;
\ No newline at end of file
diff --git a/web/src/store/index.js b/web/src/store/index.js
new file mode 100644
index 0000000..610b15e
--- /dev/null
+++ b/web/src/store/index.js
@@ -0,0 +1,56 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import {axios} from "@/utils";
+
+Vue.use(Vuex);
+
+const store = new Vuex.Store({
+ state: {
+ title: "Sub-Store",
+ isDarkMode: false,
+
+ subscriptions: {},
+ collections: {},
+
+ settings: {}
+ },
+
+ mutations: {
+ // UI
+ SET_NAV_TITLE(state, title) {
+ state.title = title;
+ },
+ SET_DARK_MODE(state, isDarkMode) {
+ state.isDarkMode = isDarkMode
+ },
+
+ // Data
+ SET_SUBSCRIPTIONS(state, subscriptions) {
+ state.subscriptions = subscriptions;
+ },
+ SET_COLLECTIONS(state, collections) {
+ state.collections = collections;
+ }
+ },
+
+ actions: {
+ // fetch subscriptions
+ async FETCH_SUBSCRIPTIONS({commit}) {
+ axios.get("/sub").then(resp => {
+ const {data} = resp.data;
+ commit("SET_SUBSCRIPTIONS", data);
+ });
+ },
+ // fetch collections
+ async FETCH_COLLECTIONS({commit}) {
+ axios.get("/collection").then(resp => {
+ const {data} = resp.data;
+ commit("SET_COLLECTIONS", data);
+ });
+ }
+ },
+
+ getters: {}
+})
+
+export default store;
\ No newline at end of file
diff --git a/web/src/utils/index.js b/web/src/utils/index.js
new file mode 100644
index 0000000..5642e1f
--- /dev/null
+++ b/web/src/utils/index.js
@@ -0,0 +1,10 @@
+import Axios from 'axios';
+export const axios = Axios.create({
+ // baseURL: 'http://sub.store/api',
+ baseURL: 'http://127.0.0.1:3000/api',
+ timeout: 1000
+});
+
+export function isEmptyObj(obj) {
+ return Object.keys(obj).length === 0;
+}
\ No newline at end of file
diff --git a/web/src/views/CollectionEditor.vue b/web/src/views/CollectionEditor.vue
new file mode 100644
index 0000000..5797bae
--- /dev/null
+++ b/web/src/views/CollectionEditor.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/Dashboard.vue b/web/src/views/Dashboard.vue
index d1eae2c..faf42ef 100644
--- a/web/src/views/Dashboard.vue
+++ b/web/src/views/Dashboard.vue
@@ -1,6 +1,5 @@
- 首页
diff --git a/web/src/views/PopUpProxyList.vue b/web/src/views/PopUpProxyList.vue
new file mode 100644
index 0000000..62b220a
--- /dev/null
+++ b/web/src/views/PopUpProxyList.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/SubEditor.vue b/web/src/views/SubEditor.vue
new file mode 100644
index 0000000..2c67689
--- /dev/null
+++ b/web/src/views/SubEditor.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/Subscription.vue b/web/src/views/Subscription.vue
index 7a27b20..03950c8 100644
--- a/web/src/views/Subscription.vue
+++ b/web/src/views/Subscription.vue
@@ -1,9 +1,112 @@
-
- 订阅
+
+
+
+ 单个订阅
+
+
+
+
+
+
+
+
+
+
+ mdi-dots-vertical
+
+
+
+
+
+
+ 组合订阅
+
+
+
+
+
+
+
+
+ {{ subs }}
+
+
+
+
+
+ mdi-dots-vertical
+
+
+
+
+
+
+
+
+ mdi-close
+ apps
+
+
+
+ mdi-plus
+
+
+
+