diff --git a/article/serializers.py b/article/serializers.py index df3fbcd..6a8e442 100644 --- a/article/serializers.py +++ b/article/serializers.py @@ -3,6 +3,7 @@ from article.models import Article, Category, Tag, Avatar from user_info.serializers import UserDescSerializer from comment.serializers import CommentSerializer + class CategorySerializer(serializers.ModelSerializer): """所有分类的序列化器""" # 将路由间的表示转换为超链接 @@ -26,6 +27,8 @@ class AvatarSerializer(serializers.ModelSerializer): class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer): """将原来的ArticleSerializer抽象出一个父类""" + # 添加文章id + id = serializers.IntegerField(read_only=True) author = UserDescSerializer(read_only=True) # 希望文章接口不仅仅只返回分类的id而已,所以需要显式指定category,将其变成一个嵌套数据, # 分类的嵌套序列化字段 diff --git a/db.sqlite3 b/db.sqlite3 index db65b93..346a3fb 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ee4f2c3..f3826c8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,13 +11,17 @@ "axios": "^0.27.2", "core-js": "^3.8.3", "jquery": "^3.6.1", - "vue": "^3.2.13" + "vue": "^3.2.13", + "vue-router": "^4.0.3", + "vuex": "^4.0.0" }, "devDependencies": { "@babel/core": "^7.12.16", "@babel/eslint-parser": "^7.12.16", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-plugin-router": "~5.0.0", + "@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-service": "~5.0.0", "eslint": "^7.32.0", "eslint-plugin-vue": "^8.0.3" @@ -2856,6 +2860,11 @@ "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true }, + "node_modules/@vue/devtools-api": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.3.0.tgz", + "integrity": "sha512-OfjtreoF3LtHmte3TrWSoZcyL4XWBL5+dTnCARuJZzTCYuaaO29PGMKCKdmXi4CZ0SiN0Exz1IGSo2S5BgDwEQ==" + }, "node_modules/@vue/reactivity": { "version": "3.2.39", "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.39.tgz", @@ -10171,6 +10180,17 @@ "node": ">=8" } }, + "node_modules/vue-router": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.5.tgz", + "integrity": "sha512-IsvoF5D2GQ/EGTs/Th4NQms9gd2NSqV+yylxIyp/OYp8xOwxmU8Kj/74E9DTSYAyH5LX7idVUngN3JSj1X4xcQ==", + "dependencies": { + "@vue/devtools-api": "^6.1.4" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz", @@ -10193,6 +10213,17 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "node_modules/vuex": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz", + "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.11" + }, + "peerDependencies": { + "vue": "^3.0.2" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.0.tgz", @@ -12766,8 +12797,7 @@ "version": "5.0.8", "resolved": "https://registry.npmmirror.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz", "integrity": "sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA==", - "dev": true, - "requires": {} + "dev": true }, "@vue/cli-service": { "version": "5.0.8", @@ -13015,6 +13045,11 @@ } } }, + "@vue/devtools-api": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.3.0.tgz", + "integrity": "sha512-OfjtreoF3LtHmte3TrWSoZcyL4XWBL5+dTnCARuJZzTCYuaaO29PGMKCKdmXi4CZ0SiN0Exz1IGSo2S5BgDwEQ==" + }, "@vue/reactivity": { "version": "3.2.39", "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.39.tgz", @@ -13273,15 +13308,13 @@ "version": "1.8.0", "resolved": "https://registry.npmmirror.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "8.2.0", @@ -13340,8 +13373,7 @@ "version": "3.5.2", "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} + "dev": true }, "ansi-colors": { "version": "4.1.3", @@ -14165,8 +14197,7 @@ "version": "6.3.1", "resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", - "dev": true, - "requires": {} + "dev": true }, "css-loader": { "version": "6.7.1", @@ -14337,8 +14368,7 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true, - "requires": {} + "dev": true }, "csso": { "version": "4.2.0", @@ -15808,8 +15838,7 @@ "version": "5.1.0", "resolved": "https://registry.npmmirror.com/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} + "dev": true }, "ieee754": { "version": "1.2.1", @@ -17179,29 +17208,25 @@ "version": "5.1.2", "resolved": "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-loader": { "version": "6.2.1", @@ -17291,8 +17316,7 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -17327,8 +17351,7 @@ "version": "5.1.0", "resolved": "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true, - "requires": {} + "dev": true }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -18822,6 +18845,14 @@ } } }, + "vue-router": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.5.tgz", + "integrity": "sha512-IsvoF5D2GQ/EGTs/Th4NQms9gd2NSqV+yylxIyp/OYp8xOwxmU8Kj/74E9DTSYAyH5LX7idVUngN3JSj1X4xcQ==", + "requires": { + "@vue/devtools-api": "^6.1.4" + } + }, "vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz", @@ -18846,6 +18877,14 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vuex": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz", + "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", + "requires": { + "@vue/devtools-api": "^6.0.0-beta.11" + } + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.0.tgz", @@ -19149,8 +19188,7 @@ "version": "8.9.0", "resolved": "https://registry.npmmirror.com/ws/-/ws-8.9.0.tgz", "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -19277,8 +19315,7 @@ "version": "7.5.9", "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "requires": {} + "dev": true }, "y18n": { "version": "5.0.8", diff --git a/frontend/package.json b/frontend/package.json index bcd3a91..cbf723e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,13 +11,17 @@ "axios": "^0.27.2", "core-js": "^3.8.3", "jquery": "^3.6.1", - "vue": "^3.2.13" + "vue": "^3.2.13", + "vue-router": "^4.0.3", + "vuex": "^4.0.0" }, "devDependencies": { "@babel/core": "^7.12.16", "@babel/eslint-parser": "^7.12.16", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-plugin-router": "~5.0.0", + "@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-service": "~5.0.0", "eslint": "^7.32.0", "eslint-plugin-vue": "^8.0.3" diff --git a/frontend/public/index.html b/frontend/public/index.html index 3e5a139..1e88518 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -7,7 +7,7 @@ <%= htmlWebpackPlugin.options.title %> - + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 47af2dd..c746211 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,29 +1,17 @@ \ No newline at end of file + diff --git a/frontend/src/assets/images/search.png b/frontend/src/assets/images/search.png new file mode 100644 index 0000000..defc015 Binary files /dev/null and b/frontend/src/assets/images/search.png differ diff --git a/frontend/src/components/ArticleList.vue b/frontend/src/components/ArticleList.vue index d97773b..44da490 100644 --- a/frontend/src/components/ArticleList.vue +++ b/frontend/src/components/ArticleList.vue @@ -1,73 +1,198 @@ + const get_article_data = () => { + let article_url = "http://127.0.0.1:6789/api/article"; // BaseUrl + let params = new URLSearchParams(); + // 注意 appendIfExists 方法是原生没有的 + // 原生只有 append 方法,但此方法不能判断值是否存在 + // 只有这两种参数存在时才加入url + params.appendIfExists('page', router.query.page); + params.appendIfExists('search', router.query.search); + // 这下可以同时传入多种参数 + const paramsString = params.toString(); + if (paramsString.charAt(0) !== '') { + article_url += '/?' + paramsString; + } + $.ajax({ + url: article_url, + type: "GET", + success: (resp) => { + articles.value = resp.results; // 获取当前页面的文章列表 + previous.value = resp.previous; // 获取前一页的链接 + next.value = resp.next; // 获取后一页的链接 + }, + }); + }; + // 在翻页后取得包括page和search的正确路径 + const get_path = (direction) => { + let url = ""; + try { + + switch(direction) { + case 'next': + if (next.value !== undefined) { + url += (new URL(next.value)).search + } + break; + case 'previous': + if (previous.value !== undefined) { + url += (new URL(previous.value)).search + } + break; + } + } + catch { return url} + return url; + } - \ No newline at end of file +#paginator a { + color: black; +} + +.current-page { + font-size: x-large; + font-weight: bold; + padding-left: 10px; + padding-right: 10px; +} + diff --git a/frontend/src/components/BlogFooter.vue b/frontend/src/components/BlogFooter.vue index c5d3c6d..05feaa8 100644 --- a/frontend/src/components/BlogFooter.vue +++ b/frontend/src/components/BlogFooter.vue @@ -1,27 +1,26 @@ diff --git a/frontend/src/components/BlogHeader.vue b/frontend/src/components/BlogHeader.vue index 0b24320..557522c 100644 --- a/frontend/src/components/BlogHeader.vue +++ b/frontend/src/components/BlogHeader.vue @@ -1,22 +1,113 @@ diff --git a/frontend/src/main.js b/frontend/src/main.js index 01433bc..8c88c13 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,4 +1,13 @@ import { createApp } from 'vue' import App from './App.vue' +import router from './router' +import store from './store' -createApp(App).mount('#app') +URLSearchParams.prototype.appendIfExists = function (key, value) { + if (value !== null && value !== undefined) { + this.append(key, value) + } +}; + + +createApp(App).use(store).use(router).mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..0572916 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,23 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomePage from '@/views/HomePageView.vue' +import ArticleDetail from '@/views/ArticleDetailView.vue' + +const routes = [ + { + path: '/', + name: 'home', + component: HomePage + }, + { + path: '/detail/:id', + name: 'detail', + component: ArticleDetail, + }, +] + +const router = createRouter({ + history: createWebHistory(), // 路径中不再有恶心的#号 + routes +}) + +export default router diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js new file mode 100644 index 0000000..f5cdea6 --- /dev/null +++ b/frontend/src/store/index.js @@ -0,0 +1,14 @@ +import { createStore } from 'vuex' + +export default createStore({ + state: { + }, + getters: { + }, + mutations: { + }, + actions: { + }, + modules: { + } +}) diff --git a/frontend/src/views/AboutView.vue b/frontend/src/views/AboutView.vue new file mode 100644 index 0000000..7054f59 --- /dev/null +++ b/frontend/src/views/AboutView.vue @@ -0,0 +1,5 @@ + diff --git a/frontend/src/views/ArticleDetailView.vue b/frontend/src/views/ArticleDetailView.vue new file mode 100644 index 0000000..ca3c2d8 --- /dev/null +++ b/frontend/src/views/ArticleDetailView.vue @@ -0,0 +1,95 @@ + + + + + + + diff --git a/frontend/src/views/HomePageView.vue b/frontend/src/views/HomePageView.vue new file mode 100644 index 0000000..8b7077c --- /dev/null +++ b/frontend/src/views/HomePageView.vue @@ -0,0 +1,22 @@ + + + + +