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 @@ - - - - + + + + + - - + + + + + + + + + + + + + @@ -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 @@ +