├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── babel.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── css │ │ ├── main.css │ │ └── main.less │ ├── font │ │ ├── font.css │ │ └── hamburgserial-xbold.ttf │ ├── icon │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── img │ │ ├── 404.svg │ │ ├── about-nsbbs.png │ │ ├── avatar-bg.png │ │ ├── badge.svg │ │ ├── default-avatar.png │ │ ├── default_avatar.png │ │ ├── level │ │ │ ├── Lv1.svg │ │ │ ├── Lv2.svg │ │ │ ├── Lv3.svg │ │ │ ├── Lv4.svg │ │ │ ├── Lv5.svg │ │ │ ├── Lv6.svg │ │ │ ├── badge-Lv1.svg │ │ │ ├── badge-Lv2.svg │ │ │ ├── badge-Lv3.svg │ │ │ ├── badge-Lv4.svg │ │ │ ├── badge-Lv5.svg │ │ │ └── badge-Lv6.svg │ │ ├── logo-lanse.png │ │ ├── logo-lanse222.png │ │ ├── logo002.png │ │ ├── message │ │ │ ├── header-message.svg │ │ │ └── notification.svg │ │ └── nan.jpg │ └── json │ │ └── nav.js ├── components │ ├── article │ │ ├── ArticleBasicInfo.vue │ │ ├── ArticleCheck.vue │ │ ├── ArticleDetail.vue │ │ ├── FrontPageArticle.vue │ │ ├── LeftButtons.vue │ │ ├── UploadImage.vue │ │ └── WriteArticle.vue │ ├── books │ │ └── NanShengValue.vue │ ├── comment │ │ ├── ArticleComment.vue │ │ ├── ChildComment.vue │ │ └── CreateComment.vue │ ├── concern │ │ └── SlideShow.vue │ ├── errorPage │ │ ├── NotFound.vue │ │ └── ServerError.vue │ ├── index │ │ ├── About.vue │ │ ├── ArticleDetailIndex.vue │ │ ├── AuthorsListIndex.vue │ │ ├── Book.vue │ │ ├── CommentDonateIndex.vue │ │ ├── Index.vue │ │ ├── LabelIndex.vue │ │ ├── LabelToArticleIndex.vue │ │ ├── ResourceIndex.vue │ │ ├── SetUpIndex.vue │ │ ├── UserCenterIndex.vue │ │ ├── WriteArticleIndex.vue │ │ ├── head │ │ │ ├── IndexHeader.vue │ │ │ ├── IndexMenu.vue │ │ │ └── IndexSider.vue │ │ └── messages │ │ │ ├── Message.vue │ │ │ ├── MessageBox.vue │ │ │ └── Notification.vue │ ├── label │ │ ├── ImageUpload.vue │ │ ├── LabelContent.vue │ │ └── LabelCreate.vue │ ├── login │ │ ├── EmailResetPassword.vue │ │ ├── Login.vue │ │ ├── MobileResetPassword.vue │ │ └── Register.vue │ ├── resource │ │ ├── ImageUpload.vue │ │ ├── ResourceContent.vue │ │ └── ResourceCreate.vue │ ├── right │ │ ├── AuthorBlock.vue │ │ ├── AuthorsList.vue │ │ ├── FilingInfo.vue │ │ ├── FollowCount.vue │ │ ├── FriendDonate.vue │ │ ├── LatestComment.vue │ │ ├── MarkdownToc.vue │ │ ├── PersonalAchievement.vue │ │ ├── ProjectIntro.vue │ │ └── RelatArticle.vue │ ├── user │ │ ├── AccountSettings.vue │ │ ├── AuthorsListContent.vue │ │ ├── BindEmail.vue │ │ ├── BindPhone.vue │ │ ├── ChangePassword.vue │ │ ├── Dynamic.vue │ │ ├── FollowAuthorsListContent.vue │ │ ├── FollowTabs.vue │ │ ├── PersonalInfoDisplay.vue │ │ ├── ProfileContent.vue │ │ ├── SetUpMenu.vue │ │ ├── UploadModal.vue │ │ └── UserTabs.vue │ └── utils │ │ ├── BackTop.vue │ │ ├── CustomEmpty.vue │ │ ├── FooterButtons.vue │ │ └── Spin.vue ├── config │ ├── config.js │ └── utils.js ├── i18n │ ├── en │ │ └── common.js │ ├── en_US.js │ ├── zh │ │ └── common.js │ └── zh_CN.js ├── main.js ├── router │ └── index.js ├── service │ ├── articleService.js │ ├── axios.js │ ├── carouselService.js │ ├── commentService.js │ ├── dynamicService.js │ ├── labelService.js │ ├── loginService.js │ ├── messageService.js │ ├── resourceService.js │ └── userService.js ├── store │ └── index.js └── utils │ └── utils.js ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | dist.zip 5 | 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 180, 3 | "semi": true 4 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // presets: ["@vue/app"], 3 | presets: [ 4 | '@vue/cli-plugin-babel/preset' 5 | ], 6 | plugins: [ 7 | // [ 8 | // "import", 9 | // {libraryName: "ant-design-vue", libraryDirectory: "es", style: true} 10 | // ] 11 | // ["import", { "libraryName": "ant-design-vue", "libraryDirectory": "es", "style": "css" }] // `style: true` 会加载 less 文件 12 | ] 13 | }; -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ns-bbs-ui", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "dev": "vue-cli-service serve", 8 | "build": "vue-cli-service build" 9 | }, 10 | "dependencies": { 11 | "ant-design-vue": "2.2.8", 12 | "axios": "^0.21.0", 13 | "compression-webpack-plugin": "5.0.0", 14 | "core-js": "^3.6.5", 15 | "echarts": "^5.1.2", 16 | "less": "2.7.2", 17 | "less-loader": "^7.0.2", 18 | "mavon-editor": "3.0.1", 19 | "moment": "^2.29.1", 20 | "qs": "^6.10.3", 21 | "serve": "^11.3.2", 22 | "uglifyjs-webpack-plugin": "^2.2.0", 23 | "vue": "3.3.4", 24 | "vue-clipboard2": "^0.3.3", 25 | "vue-clipboard3": "^2.0.0", 26 | "vue-i18n": "^8.22.1", 27 | "vue-router": "^4.0.13", 28 | "vuex": "^4.0.2", 29 | "wangeditor": "^4.7.4", 30 | "webpack-theme-color-replacer": "1.3.22" 31 | }, 32 | "devDependencies": { 33 | "@vue/cli-plugin-babel": "~4.5.0", 34 | "@vue/cli-plugin-router": "~4.5.0", 35 | "@vue/cli-plugin-vuex": "~4.5.0", 36 | "@vue/cli-service": "~4.5.0", 37 | "babel-plugin-import": "^1.13.3", 38 | "vue-template-compiler": "^2.6.11" 39 | }, 40 | "browserslist": [ 41 | "> 1%", 42 | "last 2 versions", 43 | "not dead" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 南生论坛 10 | 11 | 12 | 13 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 105 | 106 | 124 | -------------------------------------------------------------------------------- /src/assets/css/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | width: 100%; 4 | height: 100%; 5 | margin: 0; 6 | padding: 0; 7 | /*全局置灰*/ 8 | /*filter:grayscale(100%)*/ 9 | } 10 | 11 | .main-header .iconfont { 12 | font-size: 24px !important; 13 | } 14 | 15 | .ant-badge-count { 16 | top: 2px !important; 17 | right: 5px; 18 | } 19 | 20 | .ant-layout-footer { 21 | text-align: center; 22 | background: #e7e7e7!important; 23 | height: 10px 24 | } 25 | 26 | /* 修改浏览器自带的滚动条样式 */ 27 | ::-webkit-scrollbar { 28 | width: 0.35rem; 29 | height: 0.25rem; 30 | background-image: linear-gradient(#ffffff 100%, #ffffff 100%); 31 | } 32 | 33 | ::-webkit-scrollbar-track { 34 | border-radius: 0; 35 | } 36 | 37 | ::-webkit-scrollbar-thumb { 38 | background-image: linear-gradient(#3798e8 100%, #3798e8 100%); 39 | transition: all .2s; 40 | } 41 | 42 | ::-webkit-scrollbar-thumb:hover { 43 | background-color: rgba(95, 95, 95, 0.7); 44 | } -------------------------------------------------------------------------------- /src/assets/css/main.less: -------------------------------------------------------------------------------- 1 | // 引入antd的less文件,然后最后只需在入口文件里面引入当前文件即可 2 | @import "~ant-design-vue/dist/antd.less"; 3 | 4 | @colors: #fa541c #3eaf7c #13c2c2 #1869ff #722ed1 #eb2f96; 5 | #home, 6 | #components-layout-demo-custom-trigger .trigger:hover { 7 | color: @primary-color !important; 8 | } 9 | 10 | .feedback-icon-container, 11 | .components-back-top-demo-custom .ant-back-top-inner { 12 | background: @primary-color; 13 | } 14 | 15 | .ant-tree li .ant-tree-node-content-wrapper:hover .org-tree-title { 16 | background: fade(@primary-color, 50%); 17 | } 18 | 19 | .dashboard-user-map .icon-fullscreen, 20 | .map-popup-count, 21 | .selected-stastics .anticon { 22 | color: @primary-color !important; 23 | } 24 | 25 | //声明map-icons为6 int map-icons = 6 26 | .map-icons(6); 27 | //循环 for(int i = 1; i <= n; i++) 28 | .map-icons(@n, @i:1) when (@i <= @n) { 29 | //.map-icon1{} 30 | //.map-icon2{} 31 | // ... 32 | .map-icon@{i} { 33 | //background-color 从@colors数组中取第i位的值,然后用fade方法淡化至50% 34 | background-color: fade(extract(@colors, @i), 50%); 35 | } 36 | .map-icons(@n, (@i + 1)); 37 | } 38 | 39 | .map-icon { 40 | .text-container { 41 | background-color: @primary-color; 42 | } 43 | } 44 | 45 | .selected-stastics { 46 | background-color: fade(@primary-color, 20%); 47 | border: 1px solid fade(@primary-color, 50%); 48 | margin-bottom: 10px; 49 | } 50 | 51 | .ant-dropdown-menu-item, 52 | .ant-dropdown-menu-submenu-title { 53 | color: @primary-color !important; 54 | } 55 | 56 | .org-container { 57 | .iconfont { 58 | color: @primary-color; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/assets/font/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'HamburgSerial-Xbold'; 3 | src: url('./hamburgserial-xbold.ttf'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } -------------------------------------------------------------------------------- /src/assets/font/hamburgserial-xbold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/font/hamburgserial-xbold.ttf -------------------------------------------------------------------------------- /src/assets/icon/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id 2533087 */ 3 | src: url('iconfont.woff2?t=1677467908743') format('woff2'), 4 | url('iconfont.woff?t=1677467908743') format('woff'), 5 | url('iconfont.ttf?t=1677467908743') format('truetype'); 6 | } 7 | 8 | .iconfont { 9 | font-family: "iconfont" !important; 10 | font-size: 16px; 11 | font-style: normal; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | .icon-copy:before { 17 | content: "\e706"; 18 | } 19 | 20 | .icon-NotFound:before { 21 | content: "\e61d"; 22 | } 23 | 24 | .icon-gitee:before { 25 | content: "\e60c"; 26 | } 27 | 28 | .icon-applyManage:before { 29 | content: "\e642"; 30 | } 31 | 32 | .icon-rightsApply:before { 33 | content: "\e9a7"; 34 | } 35 | 36 | .icon-dynamic:before { 37 | content: "\e636"; 38 | } 39 | 40 | .icon-pendingReview:before { 41 | content: "\e609"; 42 | } 43 | 44 | .icon-reviewRejected:before { 45 | content: "\e605"; 46 | } 47 | 48 | .icon-donate:before { 49 | content: "\e657"; 50 | } 51 | 52 | .icon-qianbi:before { 53 | content: "\e60a"; 54 | } 55 | 56 | .icon-right-triangle:before { 57 | content: "\e652"; 58 | } 59 | 60 | .icon-bug:before { 61 | content: "\e8e8"; 62 | } 63 | 64 | .icon-chat:before { 65 | content: "\e635"; 66 | } 67 | 68 | .icon-Lv6:before { 69 | content: "\e774"; 70 | } 71 | 72 | .icon-Lv4:before { 73 | content: "\e76f"; 74 | } 75 | 76 | .icon-Lv3:before { 77 | content: "\e770"; 78 | } 79 | 80 | .icon-Lv1:before { 81 | content: "\e772"; 82 | } 83 | 84 | .icon-Lv2:before { 85 | content: "\e773"; 86 | } 87 | 88 | .icon-Lv5:before { 89 | content: "\e775"; 90 | } 91 | 92 | .icon-about:before { 93 | content: "\e671"; 94 | } 95 | 96 | .icon-setUp:before { 97 | content: "\e606"; 98 | } 99 | 100 | .icon-quit:before { 101 | content: "\e607"; 102 | } 103 | 104 | .icon-user-picture:before { 105 | content: "\e681"; 106 | } 107 | 108 | .icon-writeArticle:before { 109 | content: "\e608"; 110 | } 111 | 112 | .icon-follow:before { 113 | content: "\e64f"; 114 | } 115 | 116 | .icon-personal-center:before { 117 | content: "\e604"; 118 | } 119 | 120 | .icon-intro:before { 121 | content: "\e6b8"; 122 | } 123 | 124 | .icon-office:before { 125 | content: "\e8b7"; 126 | } 127 | 128 | .icon-GitHub:before { 129 | content: "\e8c6"; 130 | } 131 | 132 | .icon-achievement:before { 133 | content: "\e627"; 134 | } 135 | 136 | .icon-rise:before { 137 | content: "\e602"; 138 | } 139 | 140 | .icon-relat-article:before { 141 | content: "\ec64"; 142 | } 143 | 144 | .icon-eye:before { 145 | content: "\e68f"; 146 | } 147 | 148 | .icon-comment:before { 149 | content: "\e60e"; 150 | } 151 | 152 | .icon-like:before { 153 | content: "\e61c"; 154 | } 155 | 156 | .icon-backTop:before { 157 | content: "\e685"; 158 | } 159 | 160 | .icon-reject:before { 161 | content: "\e67c"; 162 | } 163 | 164 | .icon-feed-back:before { 165 | content: "\e699"; 166 | } 167 | 168 | .icon-resolve:before { 169 | content: "\e696"; 170 | } 171 | 172 | .icon-feedbacks:before { 173 | content: "\e62f"; 174 | } 175 | 176 | .icon-fullscreen:before { 177 | content: "\e6e5"; 178 | } 179 | 180 | .icon-visitRecord:before { 181 | content: "\e61b"; 182 | } 183 | 184 | .icon-home:before { 185 | content: "\e617"; 186 | } 187 | 188 | .icon-packUp:before { 189 | content: "\e62e"; 190 | } 191 | 192 | .icon-theme:before { 193 | content: "\e65b"; 194 | } 195 | 196 | .icon-logOut:before { 197 | content: "\e63b"; 198 | } 199 | 200 | .icon-profile:before { 201 | content: "\e601"; 202 | } 203 | 204 | .icon-bell:before { 205 | content: "\e600"; 206 | } 207 | 208 | .icon-message:before { 209 | content: "\e603"; 210 | } 211 | 212 | .icon-disable:before { 213 | content: "\e663"; 214 | } 215 | 216 | .icon-enable:before { 217 | content: "\e69e"; 218 | } 219 | 220 | .icon-minus1:before { 221 | content: "\e828"; 222 | } 223 | 224 | .icon-role:before { 225 | content: "\e74e"; 226 | } 227 | 228 | .icon-add:before { 229 | content: "\e61e"; 230 | } 231 | 232 | .icon-delete:before { 233 | content: "\e684"; 234 | } 235 | 236 | .icon-rights:before { 237 | content: "\e611"; 238 | } 239 | 240 | .icon-expand:before { 241 | content: "\e655"; 242 | } 243 | 244 | .icon-edit:before { 245 | content: "\e8c3"; 246 | } 247 | 248 | .icon-user:before { 249 | content: "\f2dc"; 250 | } 251 | 252 | .icon-rightsRole:before { 253 | content: "\e637"; 254 | } 255 | 256 | .icon-organization:before { 257 | content: "\e62a"; 258 | } 259 | 260 | .icon-dashboard:before { 261 | content: "\e714"; 262 | } 263 | 264 | .icon-projectManage:before { 265 | content: "\e610"; 266 | } 267 | 268 | -------------------------------------------------------------------------------- /src/assets/icon/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/icon/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/icon/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/icon/iconfont.woff -------------------------------------------------------------------------------- /src/assets/icon/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/icon/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/img/about-nsbbs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/img/about-nsbbs.png -------------------------------------------------------------------------------- /src/assets/img/avatar-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/img/avatar-bg.png -------------------------------------------------------------------------------- /src/assets/img/badge.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/default-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/img/default-avatar.png -------------------------------------------------------------------------------- /src/assets/img/default_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/img/default_avatar.png -------------------------------------------------------------------------------- /src/assets/img/level/Lv1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/level/Lv2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/img/level/Lv3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/level/Lv4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/level/Lv5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/img/level/Lv6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/img/level/badge-Lv1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/level/badge-Lv2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/level/badge-Lv3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/level/badge-Lv4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/level/badge-Lv5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/level/badge-Lv6.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/logo-lanse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/img/logo-lanse.png -------------------------------------------------------------------------------- /src/assets/img/logo-lanse222.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/img/logo-lanse222.png -------------------------------------------------------------------------------- /src/assets/img/logo002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/img/logo002.png -------------------------------------------------------------------------------- /src/assets/img/message/header-message.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | -------------------------------------------------------------------------------- /src/assets/img/message/notification.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | 14 | 16 | -------------------------------------------------------------------------------- /src/assets/img/nan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maliangnansheng/bbs-vue3-ui/501bc1a74ef7601a18a983d6c1013a4d77ce8975/src/assets/img/nan.jpg -------------------------------------------------------------------------------- /src/assets/json/nav.js: -------------------------------------------------------------------------------- 1 | export default { 2 | menu: [ 3 | { 4 | name: "dashboard", 5 | path: "/dashboard" 6 | }, 7 | { 8 | name: "organization", 9 | path: "/organization" 10 | }, 11 | { 12 | name: "projectManage", 13 | path: "/projectManage" 14 | }, 15 | { 16 | name: "rightsRole", 17 | path: "/rightsRole", 18 | children: [ 19 | {name: "rights", path: "/rightsRole/rights"}, 20 | {name: "role", path: "/rightsRole/role"} 21 | ] 22 | }, 23 | { 24 | name: "user", 25 | path: "/user" 26 | }, 27 | { 28 | name: "feedbacks", 29 | path: "/feedbacks" 30 | }, 31 | { 32 | name: "visitRecord", 33 | path: "/visitRecord" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /src/components/article/ArticleCheck.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 91 | 92 | 126 | -------------------------------------------------------------------------------- /src/components/article/LeftButtons.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 79 | 80 | 116 | -------------------------------------------------------------------------------- /src/components/article/UploadImage.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/comment/ArticleComment.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 88 | 89 | -------------------------------------------------------------------------------- /src/components/comment/CreateComment.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 95 | 96 | -------------------------------------------------------------------------------- /src/components/concern/SlideShow.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 61 | 62 | 79 | -------------------------------------------------------------------------------- /src/components/errorPage/NotFound.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | 38 | 69 | -------------------------------------------------------------------------------- /src/components/errorPage/ServerError.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | 39 | 70 | -------------------------------------------------------------------------------- /src/components/index/AuthorsListIndex.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/components/index/Book.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/index/CommentDonateIndex.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 39 | 40 | -------------------------------------------------------------------------------- /src/components/index/LabelIndex.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/components/index/ResourceIndex.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/components/index/SetUpIndex.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/components/index/UserCenterIndex.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/components/index/WriteArticleIndex.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/index/head/IndexSider.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /src/components/index/messages/Message.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 118 | -------------------------------------------------------------------------------- /src/components/index/messages/MessageBox.vue: -------------------------------------------------------------------------------- 1 | 19 | 76 | 77 | 200 | -------------------------------------------------------------------------------- /src/components/index/messages/Notification.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 116 | -------------------------------------------------------------------------------- /src/components/label/ImageUpload.vue: -------------------------------------------------------------------------------- 1 | 24 | 118 | 130 | -------------------------------------------------------------------------------- /src/components/label/LabelCreate.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 131 | 132 | 141 | -------------------------------------------------------------------------------- /src/components/login/Login.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 122 | 123 | -------------------------------------------------------------------------------- /src/components/resource/ImageUpload.vue: -------------------------------------------------------------------------------- 1 | 24 | 119 | 131 | -------------------------------------------------------------------------------- /src/components/right/AuthorBlock.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 81 | 82 | -------------------------------------------------------------------------------- /src/components/right/AuthorsList.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 86 | 87 | 130 | -------------------------------------------------------------------------------- /src/components/right/FilingInfo.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 55 | 56 | 69 | -------------------------------------------------------------------------------- /src/components/right/FollowCount.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/right/FriendDonate.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 58 | 59 | 86 | -------------------------------------------------------------------------------- /src/components/right/LatestComment.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 59 | 60 | 104 | -------------------------------------------------------------------------------- /src/components/right/MarkdownToc.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/right/PersonalAchievement.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 48 | 49 | -------------------------------------------------------------------------------- /src/components/right/ProjectIntro.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 81 | 82 | -------------------------------------------------------------------------------- /src/components/right/RelatArticle.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 69 | 70 | 122 | -------------------------------------------------------------------------------- /src/components/user/AuthorsListContent.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 75 | 76 | -------------------------------------------------------------------------------- /src/components/user/ChangePassword.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 149 | 150 | 167 | -------------------------------------------------------------------------------- /src/components/user/Dynamic.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 75 | 76 | -------------------------------------------------------------------------------- /src/components/user/FollowAuthorsListContent.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 71 | 72 | -------------------------------------------------------------------------------- /src/components/user/FollowTabs.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 147 | 148 | -------------------------------------------------------------------------------- /src/components/user/SetUpMenu.vue: -------------------------------------------------------------------------------- 1 | 22 | 73 | 74 | -------------------------------------------------------------------------------- /src/components/user/UploadModal.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 121 | 122 | 162 | -------------------------------------------------------------------------------- /src/components/utils/BackTop.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/utils/CustomEmpty.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/utils/FooterButtons.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 71 | 72 | 97 | -------------------------------------------------------------------------------- /src/components/utils/Spin.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | // 按照教程配置动态切换主题方法 2 | const ThemeColorReplacer = require('webpack-theme-color-replacer') 3 | const {generate} = require('@ant-design/colors/dist/index') 4 | 5 | const getAntdSerials = (color) => { 6 | // 淡化(即less的tint) 7 | const lightens = new Array(9).fill().map((t, i) => { 8 | return ThemeColorReplacer.varyColor.lighten(color, i / 10) 9 | }) 10 | const colorPalettes = generate(color) 11 | const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace('#', '')).join(',') 12 | return lightens.concat(colorPalettes).concat(rgb) 13 | } 14 | 15 | const themePluginOption = { 16 | fileName: 'css/theme-colors-[contenthash:8].css', 17 | matchColors: getAntdSerials('#1890ff'), // 主色系列 18 | // 改变样式选择器,解决样式覆盖问题 19 | changeSelector(selector) { 20 | switch (selector) { 21 | case '.ant-calendar-today .ant-calendar-date': 22 | return ':not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)' + selector 23 | case '.ant-btn:focus,.ant-btn:hover': 24 | return '.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)' 25 | case '.ant-btn.active,.ant-btn:active': 26 | return '.ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)' 27 | case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon': 28 | case '.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon': 29 | return ':not(.ant-steps-item-process)' + selector 30 | case '.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover': 31 | case '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover': 32 | return '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover' 33 | case '.ant-menu-horizontal > .ant-menu-item-selected > a': 34 | case '.ant-menu-horizontal>.ant-menu-item-selected>a': 35 | return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a' 36 | case '.ant-menu-horizontal > .ant-menu-item > a:hover': 37 | case '.ant-menu-horizontal>.ant-menu-item>a:hover': 38 | return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover' 39 | default : 40 | return selector 41 | } 42 | } 43 | } 44 | 45 | const createThemeColorReplacerPlugin = () => new ThemeColorReplacer(themePluginOption) 46 | 47 | module.exports = createThemeColorReplacerPlugin 48 | 49 | -------------------------------------------------------------------------------- /src/config/utils.js: -------------------------------------------------------------------------------- 1 | // 按照教程配置动态切换主题方法 https://blog.csdn.net/Joey_Tribiani/article/details/117420207?spm=1001.2014.3001.5501 2 | import client from "webpack-theme-color-replacer/client"; 3 | import {generate} from "@ant-design/colors/dist/index"; 4 | // import {generate} = require('@ant-design/colors/dist/index').default 5 | 6 | function getAntdSerials(color) { 7 | // 淡化(即less的tint) 8 | const lightens = new Array(9).fill().map((t, i) => { 9 | return client.varyColor.lighten(color, i / 10); 10 | }); 11 | // colorPalette变换得到颜色值 12 | const colorPalettes = generate(color); 13 | const rgb = client.varyColor.toNum3(color.replace("#", "")).join(","); 14 | return lightens.concat(colorPalettes).concat(rgb); 15 | } 16 | 17 | function changeColor(newColor) { 18 | var options = { 19 | newColors: getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors` 20 | changeUrl(cssUrl) { 21 | return `/${cssUrl}`; // while router is not `hash` mode, it needs absolute path 22 | }, 23 | }; 24 | return client.changer.changeColor(options, Promise); 25 | } 26 | 27 | export default { 28 | updateTheme: (newPrimaryColor) => { 29 | changeColor(newPrimaryColor) 30 | }, 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /src/i18n/en_US.js: -------------------------------------------------------------------------------- 1 | import common from "./en/common"; 2 | 3 | export default { 4 | menu: { 5 | dashboard: "Dashboard", 6 | organization: "Organization", 7 | projectManage: "Project Manage", 8 | rightsRole: "Rights and Role", 9 | rights: "Rights Manage", 10 | role: "Role Manage", 11 | user: "User Manage", 12 | visitRecord: "Visit Record", 13 | feedbacks: "Feedback Manage", 14 | editRole: "Edit Role", 15 | }, 16 | common, 17 | }; 18 | -------------------------------------------------------------------------------- /src/i18n/zh_CN.js: -------------------------------------------------------------------------------- 1 | import common from "./zh/common"; 2 | 3 | export default { 4 | menu: { 5 | dashboard: "仪表盘", 6 | organization: "组织架构", 7 | projectManage: "系统管理", 8 | rightsRole: "权限角色", 9 | rights: "权限管理", 10 | role: "角色管理", 11 | user: "用户管理", 12 | visitRecord: "访问记录", 13 | feedbacks: "反馈管理", 14 | editRole: "编辑角色", 15 | }, 16 | common, 17 | }; 18 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App'; 3 | import Antd from 'ant-design-vue'; 4 | import 'ant-design-vue/dist/antd.css'; 5 | 6 | import router from './router' 7 | import store from './store' 8 | import "@/assets/css/main.css" 9 | // 引入自定义全局字体样式表 10 | import '@/assets/font/font.css' 11 | // 引入iconfont的样式(iconfont的使用参照iconfont官网) 12 | import '@/assets/icon/iconfont.css' 13 | // 引入axios 初始化axios里面的配置 14 | import "@/service/axios" 15 | import moment from "moment" 16 | import "./assets/css/main.less" 17 | import utils from "./utils/utils" 18 | import mavonEditor from 'mavon-editor' 19 | import 'mavon-editor/dist/css/index.css' 20 | // import routerHistory from '@/router/vueRouterHistory'; 21 | 22 | // import VueClipboards from 'vue-clipboard2' 23 | 24 | // Vue.config.productionTip = false; 25 | 26 | // // 引入自定义工具模块 27 | // Vue.prototype.$utils = utils 28 | // Vue.prototype.$moment = moment 29 | // // 将国际化添加为Vue的原型上的方法 30 | // Vue.prototype.$t = store.state.translate.bind(store.state) 31 | 32 | // Vue.use(Antd); 33 | // Vue.use(mavonEditor) 34 | // Vue.use(VueClipboards); 35 | 36 | // export default new Vue({ 37 | // router, 38 | // store, 39 | // render: h => h(App) 40 | // }).$mount('#app') 41 | 42 | 43 | 44 | const app = createApp(App); 45 | app.config.globalProperties.$utils = utils; 46 | app.config.globalProperties.$moment = moment; 47 | app.config.globalProperties.$t = store.state.translate.bind(store.state); 48 | 49 | app 50 | .use(Antd) 51 | .use(store) 52 | .use(router) 53 | .use(mavonEditor) 54 | .mount('#app'); -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | // import Vue from "vue"; 2 | // import VueRouter from "vue-router"; 3 | import { createRouter, RouteRecordRaw, createWebHistory } from 'vue-router'; 4 | 5 | // 每次调用Vue.$router.push方法跳转路由的时候先判断是不是已经在目标路由,避免重复跳转(Vue会有警告) 6 | // const originalPush = VueRouter.prototype.push; 7 | // VueRouter.prototype.push = function push(location) { 8 | // return originalPush.call(this, location).catch(err => err); 9 | // }; 10 | 11 | // Vue.use(VueRouter); 12 | 13 | const routes = [ 14 | { 15 | path: "/", 16 | name: "home", 17 | component: () => import("@/components/index/Index"), 18 | }, 19 | { 20 | path: "/search", 21 | component: () => import("@/components/index/Index"), 22 | }, 23 | { 24 | path: "/recommended", 25 | name: "recommended", 26 | component: () => import("@/components/index/AuthorsListIndex"), 27 | }, 28 | { 29 | path: "/write", 30 | component: () => import("@/components/index/WriteArticleIndex"), 31 | }, 32 | { 33 | path: "/edit/:id", 34 | component: () => import("@/components/index/WriteArticleIndex"), 35 | }, 36 | { 37 | path: "/detail/:id", 38 | name: "detail", 39 | component: () => import("@/components/index/ArticleDetailIndex"), 40 | }, 41 | { 42 | path: "/empty", 43 | component: () => import("@/components/utils/CustomEmpty"), 44 | }, 45 | { 46 | path: "/user/:id", 47 | component: () => import("@/components/index/UserCenterIndex"), 48 | children: [ 49 | { 50 | path: ":userCenterTab", 51 | component: () => import("@/components/index/UserCenterIndex") 52 | }, 53 | ] 54 | }, 55 | { 56 | path: "/label", 57 | name: "label", 58 | component: () => import("@/components/index/LabelIndex"), 59 | }, 60 | { 61 | path: "/label/:id", 62 | component: () => import("@/components/index/LabelToArticleIndex"), 63 | }, 64 | { 65 | path: "/settings", 66 | component: () => import("@/components/index/SetUpIndex"), 67 | children: [ 68 | { 69 | path: "", 70 | redirect: "profile" 71 | }, 72 | { 73 | path: "profile", 74 | name: "profile", 75 | component: () => import("@/components/user/ProfileContent") 76 | }, 77 | { 78 | path: "account", 79 | name: "account", 80 | component: () => import("@/components/user/AccountSettings") 81 | }, 82 | ] 83 | }, 84 | { 85 | path: "/resource", 86 | name: "resource", 87 | component: () => import("@/components/index/ResourceIndex"), 88 | }, 89 | { 90 | path: "/book", 91 | name: "book", 92 | component: () => import("@/components/index/Book"), 93 | }, 94 | { 95 | path: "/about", 96 | name: "about", 97 | component: () => import("@/components/index/About"), 98 | }, 99 | { 100 | path: "/commentDonate", 101 | name: "commentDonate", 102 | component: () => import("@/components/index/CommentDonateIndex"), 103 | }, 104 | { 105 | path: "/500", 106 | name: '500', 107 | component: () => import("@/components/errorPage/ServerError") 108 | }, 109 | { 110 | // 将匹配所有内容并将其放在 `$route.params.pathMatch` 下 111 | path: "/:pathMatch(.*)*", 112 | name: '404', 113 | component: () => import("@/components/errorPage/NotFound") 114 | } 115 | ]; 116 | 117 | // const router = new VueRouter({ 118 | // mode: "history", 119 | // base: process.env.BASE_URL, 120 | // routes 121 | // }); 122 | 123 | const router = createRouter({ 124 | // history: createWebHashHistory(), 125 | history: createWebHistory(), 126 | routes, 127 | }); 128 | 129 | export default router; 130 | -------------------------------------------------------------------------------- /src/service/articleService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | // 获取文章 5 | getArticleList(params) { 6 | return new Promise((resolve, reject) => { 7 | axios.get("/api/bbs/article/getList", {params}) 8 | .then((res) => resolve(res)) 9 | .catch((err) => reject(err)); 10 | }); 11 | }, 12 | // 获取个人发布的文章(所有) 13 | getPersonalArticles(params) { 14 | return new Promise((resolve, reject) => { 15 | axios.get("/api/bbs/article/getPersonalArticles", {params}) 16 | .then((res) => resolve(res)) 17 | .catch((err) => reject(err)); 18 | }); 19 | }, 20 | // 获取待审核的文章 21 | getPendingReviewArticles(params) { 22 | return new Promise((resolve, reject) => { 23 | axios.get("/api/bbs/article/getPendingReviewArticles", {params}) 24 | .then((res) => resolve(res)) 25 | .catch((err) => reject(err)); 26 | }); 27 | }, 28 | // 获取禁用的文章 29 | getDisabledArticles(params) { 30 | return new Promise((resolve, reject) => { 31 | axios.get("/api/bbs/article/getDisabledArticles", {params}) 32 | .then((res) => resolve(res)) 33 | .catch((err) => reject(err)); 34 | }); 35 | }, 36 | // 修改文章审批状态 37 | updateState(data) { 38 | return new Promise((resolve, reject) => { 39 | axios.post("/api/bbs/article/updateState", data) 40 | .then((res) => resolve(res)) 41 | .catch((err) => reject(err)); 42 | }); 43 | }, 44 | // 获取点赞过的文章 45 | getLikesArticle(params) { 46 | return new Promise((resolve, reject) => { 47 | axios.get("/api/bbs/article/getLikesArticle", {params}) 48 | .then((res) => resolve(res)) 49 | .catch((err) => reject(err)); 50 | }); 51 | }, 52 | // 获取文章评论访问总数 53 | getArticleCommentVisitTotal(params) { 54 | return new Promise((resolve, reject) => { 55 | axios.get("/api/bbs/article/getArticleCommentVisitTotal", {params}) 56 | .then((res) => resolve(res)) 57 | .catch((err) => reject(err)); 58 | }); 59 | }, 60 | // 上传图片(一张) 61 | uploadPicture(data) { 62 | return new Promise((resolve, reject) => { 63 | axios.post("/api/bbs/article/uploadPicture", data) 64 | .then((res) => resolve(res)) 65 | .catch((err) => reject(err)); 66 | }); 67 | }, 68 | // 写文章 69 | articleCreate(data) { 70 | return new Promise((resolve, reject) => { 71 | axios.post("/api/bbs/article/create", data) 72 | .then((res) => resolve(res)) 73 | .catch((err) => reject(err)); 74 | }); 75 | }, 76 | // 更新文章 77 | articleUpdate(data) { 78 | return new Promise((resolve, reject) => { 79 | axios.post("/api/bbs/article/update", data) 80 | .then((res) => resolve(res)) 81 | .catch((err) => reject(err)); 82 | }); 83 | }, 84 | // 获取文章详情 85 | getArticleById(params) { 86 | return new Promise((resolve, reject) => { 87 | axios.get("/api/bbs/article/getById", {params}) 88 | .then((res) => resolve(res)) 89 | .catch((err) => reject(err)); 90 | }); 91 | }, 92 | // 获取文章一些统计数据 93 | getArticleCountById(params) { 94 | return new Promise((resolve, reject) => { 95 | axios.get("/api/bbs/article/getCountById", {params}) 96 | .then((res) => resolve(res)) 97 | .catch((err) => reject(err)); 98 | }); 99 | }, 100 | // 文章置顶/取消置顶 101 | articleTop(params) { 102 | return new Promise((resolve, reject) => { 103 | axios.get("/api/bbs/article/articleTop", {params}) 104 | .then((res) => resolve(res)) 105 | .catch((err) => reject(err)); 106 | }); 107 | }, 108 | //删除文章 109 | articleDelete(data) { 110 | return new Promise((resolve, reject) => { 111 | axios.post("/api/bbs/article/delete/" + data) 112 | .then((res) => resolve(res)) 113 | .catch((err) => reject(err)); 114 | }); 115 | }, 116 | // 文章审核数据量 117 | getArticleCheckCount(params) { 118 | return new Promise((resolve, reject) => { 119 | axios.get("/api/bbs/article/getArticleCheckCount", {params}) 120 | .then((res) => resolve(res)) 121 | .catch((err) => reject(err)); 122 | }); 123 | }, 124 | }; 125 | -------------------------------------------------------------------------------- /src/service/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import store from "@/store/index"; 3 | // 设置xhr请求超时时间和baseURL(毫秒) 4 | axios.defaults.timeout = 15000; 5 | if (process.env.NODE_ENV === "production") { 6 | // axios.defaults.baseURL = "http://bbs.nansin.top"; 7 | } 8 | axios.defaults.withCredentials = true; //允许axios请求携带cookie等凭证 9 | export default (() => { 10 | 11 | // 每次请求前处理 12 | axios.interceptors.request.use( 13 | function (config) { 14 | return config; 15 | }, 16 | function (error) { 17 | return Promise.reject(error); 18 | } 19 | ); 20 | 21 | // 每次请求回来的处理 22 | axios.interceptors.response.use( 23 | function (response) { 24 | // 后台会在响应头带上用户头像链接,每次和存在store中的比较,不同就替换,实现头像更新 25 | if (response.headers["x-user-picture"]) { 26 | store.state.picture = response.headers["x-user-picture"]; 27 | } 28 | // 后台会在响应头带上用户任务提醒和消息通知的数量,存在store里面, 29 | if (response.headers["x-system-notify-count"]) { 30 | store.state.systemNotifyCount = response.headers["x-system-notify-count"]; 31 | } 32 | if (response.headers["x-task-notify-count"]) { 33 | store.state.taskNotifyCount = response.headers["x-task-notify-count"]; 34 | } 35 | // 和后台约定好响应码为200且响应体的code字段为0的时候才算成功 36 | if (response.status === 200) { 37 | if (response.data) { 38 | if (response.data.code === 0) { 39 | return Promise.resolve(response.data); 40 | 41 | // 如果code是302,代表需要跳转到切页面 42 | } else if (response.data.code === 302 && response.config.url !== '/api/bbs/user/getCurrentUserRights') { 43 | // window.location.href = response.data.data.target; 44 | store.state.isLogin = false; 45 | store.state.loginVisible = true; 46 | return Promise.reject(response.data); 47 | } else { 48 | throw response.data; 49 | } 50 | } else { 51 | throw response; 52 | } 53 | } else { 54 | // 响应码不是200则返回一个失败的Promise 55 | return Promise.reject(response); 56 | } 57 | }, 58 | function (error) { 59 | return Promise.reject(error); 60 | } 61 | ); 62 | })(); 63 | -------------------------------------------------------------------------------- /src/service/carouselService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | // 获取走马灯 5 | getCarouselList(params) { 6 | return new Promise((resolve, reject) => { 7 | axios.get("/api/bbs/carousel/getList", {params}) 8 | .then((res) => resolve(res)) 9 | .catch((err) => reject(err)); 10 | }); 11 | }, 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /src/service/commentService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | // 获取文章的评论信息 5 | getCommentByArticleId(params) { 6 | return new Promise((resolve, reject) => { 7 | axios.get("/api/bbs/comment/getCommentByArticleId", {params}) 8 | .then((res) => resolve(res)) 9 | .catch((err) => reject(err)); 10 | }); 11 | }, 12 | // 获取最新评论信息 13 | getLatestComment(params) { 14 | return new Promise((resolve, reject) => { 15 | axios.get("/api/bbs/comment/getLatestComment", {params}) 16 | .then((res) => resolve(res)) 17 | .catch((err) => reject(err)); 18 | }); 19 | }, 20 | // 创建评论 21 | createComment(data) { 22 | return new Promise((resolve, reject) => { 23 | axios.post("/api/bbs/comment/create", data) 24 | .then((res) => resolve(res)) 25 | .catch((err) => reject(err)); 26 | }); 27 | }, 28 | // 删除评论 29 | deleteComment(data) { 30 | return new Promise((resolve, reject) => { 31 | axios.post("/api/bbs/comment/delete/" + data) 32 | .then((res) => resolve(res)) 33 | .catch((err) => reject(err)); 34 | }); 35 | }, 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /src/service/dynamicService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | // 获取动态 5 | getDynamicList(params) { 6 | return new Promise((resolve, reject) => { 7 | axios.get("/api/bbs/dynamic/getList", {params}) 8 | .then((res) => resolve(res)) 9 | .catch((err) => reject(err)); 10 | }); 11 | }, 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /src/service/labelService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | // 获取标签 5 | getLabelList(params) { 6 | return new Promise((resolve, reject) => { 7 | axios.get("/api/bbs/label/getList", {params}) 8 | .then((res) => resolve(res)) 9 | .catch((err) => reject(err)); 10 | }); 11 | }, 12 | // 上传标签logo 13 | uploadLabelLogo(data) { 14 | return new Promise((resolve, reject) => { 15 | axios.post("/api/bbs/label/uploadLabelLogo", data) 16 | .then((res) => resolve(res)) 17 | .catch((err) => reject(err)); 18 | }); 19 | }, 20 | // 新增标签 21 | labelCreate(data) { 22 | return new Promise((resolve, reject) => { 23 | axios.post("/api/bbs/label/create", data) 24 | .then((res) => resolve(res)) 25 | .catch((err) => reject(err)); 26 | }); 27 | }, 28 | // 更新标签 29 | labelUpdate(data) { 30 | return new Promise((resolve, reject) => { 31 | axios.post("/api/bbs/label/update", data) 32 | .then((res) => resolve(res)) 33 | .catch((err) => reject(err)); 34 | }); 35 | }, 36 | // 删除标签 37 | labelDelete(data) { 38 | return new Promise((resolve, reject) => { 39 | axios.post("/api/bbs/label/delete/" + data) 40 | .then((res) => resolve(res)) 41 | .catch((err) => reject(err)); 42 | }); 43 | }, 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /src/service/loginService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | export default { 4 | // 注册 5 | register(data) { 6 | return new Promise((resolve, reject) => { 7 | axios.post("/api/bbs/sso/register", data) 8 | .then(res => resolve(res)) 9 | .catch(err => reject(err)); 10 | }); 11 | }, 12 | // 登录 13 | login(data) { 14 | return new Promise((resolve, reject) => { 15 | axios.post("/api/bbs/sso/login", data) 16 | .then(res => resolve(res)) 17 | .catch(err => reject(err)); 18 | }); 19 | }, 20 | // 退出登录 21 | logout(data) { 22 | return new Promise((resolve, reject) => { 23 | axios.get("/api/bbs/sso/logout", data) 24 | .then(res => resolve(res)) 25 | .catch(err => reject(err)) 26 | }) 27 | } 28 | } -------------------------------------------------------------------------------- /src/service/messageService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | // 分页获取通知信息 5 | getMessageList(params) { 6 | return new Promise((resolve, reject) => { 7 | axios.get("/api/bbs/notify/getList", {params}) 8 | .then(res => resolve(res)) 9 | .catch(err => reject(err)); 10 | }); 11 | }, 12 | // 全部已读 13 | makeAllRead(params) { 14 | return new Promise((resolve, reject) => { 15 | axios.post("/api/bbs/notify/haveRead", null, {params}) 16 | .then(res => resolve(res)) 17 | .catch(err => reject(err)); 18 | }); 19 | }, 20 | // 标记已读 21 | markRead(data) { 22 | return new Promise((resolve, reject) => { 23 | axios.post("/api/bbs/notify/markRead", data) 24 | .then(res => resolve(res)) 25 | .catch(err => reject(err)); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/service/resourceService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | // 获取资源导航 5 | getResourceList(params) { 6 | return new Promise((resolve, reject) => { 7 | axios.get("/api/bbs/resource/getList", {params}) 8 | .then((res) => resolve(res)) 9 | .catch((err) => reject(err)); 10 | }); 11 | }, 12 | // 获取资源导航 13 | getCategorys(params) { 14 | return new Promise((resolve, reject) => { 15 | axios.get("/api/bbs/resource/getCategorys", {params}) 16 | .then((res) => resolve(res)) 17 | .catch((err) => reject(err)); 18 | }); 19 | }, 20 | // 上传资源导航logo 21 | uploadResourceLogo(data) { 22 | return new Promise((resolve, reject) => { 23 | axios.post("/api/bbs/resource/uploadResourceLogo", data) 24 | .then((res) => resolve(res)) 25 | .catch((err) => reject(err)); 26 | }); 27 | }, 28 | // 新增资源导航 29 | resourceCreate(data) { 30 | return new Promise((resolve, reject) => { 31 | axios.post("/api/bbs/resource/create", data) 32 | .then((res) => resolve(res)) 33 | .catch((err) => reject(err)); 34 | }); 35 | }, 36 | // 更新资源导航 37 | resourceUpdate(data) { 38 | return new Promise((resolve, reject) => { 39 | axios.post("/api/bbs/resource/update", data) 40 | .then((res) => resolve(res)) 41 | .catch((err) => reject(err)); 42 | }); 43 | }, 44 | // 删除资源导航 45 | resourceDelete(data) { 46 | return new Promise((resolve, reject) => { 47 | axios.post("/api/bbs/resource/delete/" + data) 48 | .then((res) => resolve(res)) 49 | .catch((err) => reject(err)); 50 | }); 51 | }, 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | // import Vue from "vue"; 2 | // import Vuex from "vuex"; 3 | import zh_CN from "@/i18n/zh_CN"; 4 | import en_US from "@/i18n/en_US"; 5 | import userService from "@/service/userService"; 6 | import utils from "@/config/utils"; 7 | import { createStore } from 'vuex'; 8 | 9 | const langs = {zh_CN, en_US}; 10 | 11 | // Vue.use(Vuex); 12 | 13 | const state = { 14 | // 判断用户是否已经登录 15 | isLogin: false, 16 | // 登录modal是否可见 17 | loginVisible: false, 18 | // 注册modal是否可见 19 | registerVisible: false, 20 | // 手机找回密码modal是否可见 21 | mobileResetPasswordVisible: false, 22 | // 邮箱找回密码modal是否可见 23 | emailResetPasswordVisible: false, 24 | // 登录用户id 25 | userId: "", 26 | // 用户头像 27 | picture: "", 28 | // 判断用户是否是管理员 29 | isManage: false, 30 | // 文章审核(enable、disabled、pendingReview) 31 | articleCheck: "enable", 32 | // 用户名称长度限制 33 | userMaxLength: 10, 34 | // 主题色 35 | colorOptions: ["#000000", "#3eaf7c", "#13c2c2", "#1869ff", "#722ed1", "#eb2f96"], 36 | // 当前使能的主题色 37 | themeColor: "#13c2c2", 38 | // 是否启用跑马灯(1:启用,0:禁止) 39 | isCarousel: 1, 40 | // 菜单是否收缩 41 | collapsed: false, 42 | // 比collapsed大一点 43 | collapsedMax: false, 44 | // 用户屏幕宽度 45 | width: 0, 46 | // 用户屏幕高度 47 | height: 0, 48 | // 语言 49 | locale: "zh_CN", 50 | // 系统通知数量 51 | systemNotifyCount: 0, 52 | // 任务提醒数量 53 | taskNotifyCount: 0, 54 | // 南生运营域名 55 | manageDomain: 'http://manage-test.nansin.top', 56 | // 国际化方法 57 | translate: function (val) { 58 | // 国际化方法 59 | if (!val) { 60 | return ""; 61 | } 62 | const arr = val.split("."); 63 | let l = arr.length; 64 | let re; 65 | try { 66 | re = langs[this.locale]; 67 | for (let i = 0; i < l; i++) { 68 | re = re[arr[i]]; 69 | } 70 | } catch (err) { 71 | re = arr[l - 1]; 72 | } 73 | return re || arr[l - 1]; 74 | } 75 | }; 76 | const getters = { 77 | formCol(state) { 78 | if (state.width >= 500) { 79 | return {label: 6, wrapper: 16}; 80 | } 81 | return {label: 8, wrapper: 16}; 82 | }, 83 | // 自动计算屏幕的内容区域(减取padding、margin和菜单栏的宽度) 84 | contentWidth(state) { 85 | if (state.collapsed) { 86 | return state.width - 120; 87 | } 88 | return state.width - 314; 89 | }, 90 | isPc(state) { 91 | // 根据用户屏幕宽度判断是pc访问还是移动设备访问 92 | if (state.width > 750) { 93 | return true; 94 | } 95 | return false; 96 | }, 97 | // 状态枚举,项目中多出用到,所以存到store里面使用 98 | stateList(state) { 99 | return [ 100 | {title: state.translate("common.enabled"), value: 1}, 101 | {title: state.translate("common.disabled"), value: 0} 102 | ]; 103 | } 104 | }; 105 | const mutations = { 106 | // 更改主题色,同时将配置存在localStorage 107 | changeColor(state, color) { 108 | utils.updateTheme(color); 109 | window.localStorage.themeColor = color; 110 | state.themeColor = color; 111 | } 112 | }; 113 | export default createStore({ 114 | state, 115 | mutations, 116 | getters, 117 | }); 118 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | export default { 2 | isClamp(event) { 3 | /** 4 | * @param {*} event 事件对象 5 | * @returns {Boolean} true: 说明超出元素宽度显示 false: 说明并未超出显示 6 | */ 7 | try { 8 | const width = event.target.clientWidth; 9 | const originWidth = event.target.nextElementSibling.clientWidth; 10 | return originWidth > width; 11 | } catch (error) { 12 | return false; 13 | } 14 | }, 15 | 16 | // 元素onscroll事件的回调函数,用于滚动加载 17 | scroll(el) { 18 | /** 19 | * @this {VM} this指向的是Vue实例对象,因为是在vue实例(组件)里面通过call绑定this后调用的, 20 | * 这样hasNext、finish等参数就不需要传过来,直接在this身上取 21 | * @argument {DomElement} el 需要监听滚动事件的dom元素(容器) 22 | * @argument {Number} height 高度 23 | */ 24 | 25 | 26 | el.onscroll = ({ target }) => { 27 | /** 28 | * scrollTop:滚动条滚动距离; 29 | * scrollHeight:文档内容实际高度(包括超出视窗的溢出部分); 30 | * clientHeight:窗口可视范围高度 31 | */ 32 | const {scrollTop, scrollHeight, clientHeight} = target; 33 | /** 34 | * @property hasNext 列表是否还有没加载完的(是否已经加载完最后一项),是后台返回的结果 35 | * @property finish 上个请求是否完成,完成后才可以进行下个请求 36 | * @function loadMore 加载下一页的方法 37 | * 100 可不加 38 | */ 39 | if (this.hasNext && this.finish && scrollHeight - scrollTop <= clientHeight + 100) { 40 | this.loadMore(); 41 | } 42 | }; 43 | }, 44 | 45 | // 按照创建时间和当前时间显示操作(刚刚,几小时/分/天/周/月/年前) 46 | showtime(time) { 47 | let date = typeof time === "number" ? new Date(time) : new Date((time || "").replace(/-/g, "/")); 48 | // 秒 49 | let secondTime = (new Date().getTime() - date.getTime()) / 1000; 50 | // 天 51 | let dayTime = Math.floor(secondTime / 86400); 52 | 53 | let isValidDate = Object.prototype.toString.call(date) === "[object Date]" && !isNaN(date.getTime()); 54 | 55 | if (!isValidDate) { 56 | window.console.error("不是有效日期格式"); 57 | } 58 | const formatDate = function (date) { 59 | let today = new Date(date); 60 | let year = today.getFullYear(); 61 | let month = ("0" + (today.getMonth() + 1)).slice(-2); 62 | let day = ("0" + today.getDate()).slice(-2); 63 | let hour = today.getHours(); 64 | let minute = today.getMinutes(); 65 | let second = today.getSeconds(); 66 | return `${year}-${month}-${day} ${hour}:${minute}:${second}`; 67 | }; 68 | 69 | // 异常情况 70 | if (isNaN(dayTime) || dayTime < 0) { 71 | return formatDate(date); 72 | } 73 | 74 | return ( 75 | (dayTime === 0 && ((secondTime < 60 && "刚刚") || 76 | (secondTime < 120 && "1分钟前") || 77 | (secondTime < 3600 && Math.floor(secondTime / 60) + "分钟前") || 78 | (secondTime < 7200 && "1小时前") || 79 | (secondTime < 86400 && Math.floor(secondTime / 3600) + "小时前"))) || 80 | (dayTime === 1 && "昨天") || 81 | (dayTime < 7 && dayTime + "天前") || 82 | (dayTime < 30 && Math.floor(dayTime / 7) + "周前") || 83 | (dayTime < 365 && Math.floor(dayTime / 30) + "月前") || 84 | (dayTime >= 365 && Math.floor(dayTime / 365) + "年前") 85 | ); 86 | }, 87 | 88 | // 将h标签转化为ul>li的形式 89 | toToc(data) { 90 | // 过滤出标签 91 | data = data.match(/<[hH][1-6]>.*?<\/[hH][1-6]>/g); 92 | if (!data) { 93 | return null; 94 | } 95 | const levelStack = []; 96 | let result = ''; 97 | const addStartUL = function () { 98 | result += '\n'; 102 | }; 103 | const addLI = function (index, itemText) { 104 | result += '
  • ' + itemText + "
  • \n"; 105 | }; 106 | data.forEach(function (item, index) { 107 | // 获取a标签的id值 108 | let a_id = item.replace(//g, ''); 109 | // 匹配h标签的文字 110 | const itemText = item.replace(/<[^>]+>/g, ''); 111 | // 匹配h?标签 112 | const itemLabel = item.match(/<\w+?>/)[0]; 113 | // 判断数组里有无 114 | let levelIndex = levelStack.indexOf(itemLabel); 115 | // 没有找到相应标签,则将新增ul、li 116 | if (levelIndex === -1) { 117 | levelStack.unshift(itemLabel); 118 | addStartUL(); 119 | addLI(a_id, itemText); 120 | } 121 | // 找到了相应标签,并且在栈顶的位置则直接将li放在此ul下 122 | else if (levelIndex === 0) { 123 | addLI(a_id, itemText); 124 | } 125 | // 找到了相应标签,但是不在栈顶位置,需要将之前的所有出栈并且打上闭合标签,最后新增li 126 | else { 127 | while (levelIndex--) { 128 | levelStack.shift(); 129 | addEndUL(); 130 | } 131 | addLI(a_id, itemText); 132 | } 133 | }); 134 | // 如果栈中还有,全部出栈打上闭合标签 135 | while (levelStack.length) { 136 | levelStack.shift(); 137 | addEndUL(); 138 | } 139 | 140 | // 去掉回车换行 141 | return result.replace(/\r?\n|\r/g, ""); 142 | }, 143 | 144 | }; 145 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const UglyfyJsPlugin = require("uglifyjs-webpack-plugin"); 2 | const CompressionPlugin = require("compression-webpack-plugin"); 3 | const createThemeColorReplacerPlugin = require("./src/config/config"); 4 | const vueConfig = { 5 | // 开发环境的跨域配置 6 | devServer: { 7 | // proxy: "http://bbs.localhost.com", 8 | disableHostCheck: true, 9 | port: 8082, 10 | }, 11 | // css样式配置(为了实现动态切换主题),教程 https://blog.csdn.net/Joey_Tribiani/article/details/117420207?spm=1001.2014.3001.5501 12 | css: { 13 | loaderOptions: { 14 | less: { 15 | lessOptions: { 16 | modifyVars: {}, 17 | javascriptEnabled: true 18 | } 19 | } 20 | } 21 | }, 22 | assetsDir: "static", 23 | productionSourceMap: false, 24 | // configureWebpack里面是webpack配置项 25 | configureWebpack: { 26 | optimization: { 27 | minimizer: [ 28 | // 压缩js文件 29 | new UglyfyJsPlugin({ 30 | test: /\.js(\?.*)?$/i 31 | }) 32 | ] 33 | }, 34 | plugins: [ 35 | // 使用gzip压缩 36 | new CompressionPlugin({ 37 | algorithm: "gzip", 38 | test: /\.js$|\.html$|\.css$/, // 匹配文件名 39 | filename: "[path].gz[query]", // 压缩后的文件名(保持原文件名,后缀加.gz) 40 | minRatio: 1, // 压缩率小于1才会压缩 41 | threshold: 10240, // 对超过10k的数据压缩 42 | deleteOriginalAssets: false // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件) 43 | }), 44 | // 动态切换主题插件 45 | createThemeColorReplacerPlugin() 46 | ], 47 | // 外链cdn引入 48 | externals: { 49 | // vue: "Vue", 50 | "vue2-leaflet": "Vue2Leaflet", 51 | leaflet: "L" 52 | } 53 | } 54 | }; 55 | module.exports = vueConfig; 56 | --------------------------------------------------------------------------------