add blog-admin part
add server\admin UI server: blog manage\label manage\message manage\upload\user admin UI:login mode\home\user\auth\label\article\
Showing
87 changed files
with
6773 additions
and
0 deletions
rf-blog/code/admin/index.html
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html> | ||
3 | +<head> | ||
4 | + <meta charset="UTF-8"> | ||
5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=2.0, user-scalable=0" /> | ||
6 | + <title>Document</title> | ||
7 | +</head> | ||
8 | +<body> | ||
9 | + <div id="app"></div> | ||
10 | +</body> | ||
11 | +</html> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/admin/src/App.vue
0 → 100644
rf-blog/code/admin/src/api/blog.js
0 → 100644
1 | +import axios from "utils/request"; | ||
2 | + | ||
3 | +/** | ||
4 | + * 获取博客列表 | ||
5 | + * @param data | ||
6 | + * @returns {AxiosPromise} | ||
7 | + */ | ||
8 | +export function apiGetBlogList(params) { | ||
9 | + return axios.get("/blog/list", params); | ||
10 | +} | ||
11 | + | ||
12 | +/** | ||
13 | + * 获取博客详情 | ||
14 | + * @param data | ||
15 | + * @returns {AxiosPromise} | ||
16 | + */ | ||
17 | +export function apiGetBlogDetail(params) { | ||
18 | + return axios.get("/blog/info", params); | ||
19 | +} | ||
20 | + | ||
21 | +/** | ||
22 | + * 新增博客 | ||
23 | + * @param data | ||
24 | + * @returns {AxiosPromise} | ||
25 | + */ | ||
26 | +export function apiAddBlog(params) { | ||
27 | + return axios.postFile("/blog/add", params); | ||
28 | +} | ||
29 | +/** | ||
30 | + * 修改博客 | ||
31 | + * @param data | ||
32 | + * @returns {AxiosPromise} | ||
33 | + */ | ||
34 | +export function apiUpdateBlog(params) { | ||
35 | + return axios.postFile("/blog/update", params); | ||
36 | +} | ||
37 | +/** | ||
38 | + * 删除博客 | ||
39 | + * @param data | ||
40 | + * @returns {AxiosPromise} | ||
41 | + */ | ||
42 | +export function apiDelBlog(params) { | ||
43 | + return axios.get("/blog/del", params); | ||
44 | +} |
rf-blog/code/admin/src/api/label.js
0 → 100644
1 | +import axios from "utils/request"; | ||
2 | + | ||
3 | +/** | ||
4 | + * 获取标签列表 | ||
5 | + * @param data | ||
6 | + * @returns {AxiosPromise} | ||
7 | + */ | ||
8 | +export function apiGetLabelList(params) { | ||
9 | + return axios.get("/label/list", params); | ||
10 | +} | ||
11 | +/** | ||
12 | + * 新增标签 | ||
13 | + * @param data | ||
14 | + * @returns {AxiosPromise} | ||
15 | + */ | ||
16 | +export function apiAddLabel(params) { | ||
17 | + return axios.postFile("/label/add", params); | ||
18 | +} | ||
19 | +/** | ||
20 | + * 修改标签 | ||
21 | + * @param data | ||
22 | + * @returns {AxiosPromise} | ||
23 | + */ | ||
24 | +export function apiUpdateLabel(params) { | ||
25 | + return axios.post("/label/update", params); | ||
26 | +} | ||
27 | +/** | ||
28 | + * 删除标签 | ||
29 | + * @param data | ||
30 | + * @returns {AxiosPromise} | ||
31 | + */ | ||
32 | +export function apiDelLabel(params) { | ||
33 | + return axios.get("/label/del", params); | ||
34 | +} |
rf-blog/code/admin/src/api/message.js
0 → 100644
1 | +import axios from "utils/request"; | ||
2 | + | ||
3 | +/** | ||
4 | + * 获取留言列表 | ||
5 | + * @param data | ||
6 | + * @returns {AxiosPromise} | ||
7 | + */ | ||
8 | +export function apiGetMessageList(params) { | ||
9 | + return axios.get("/message/list", params); | ||
10 | +} | ||
11 | + | ||
12 | +/** | ||
13 | + * 删除留言 | ||
14 | + * @param data | ||
15 | + * @returns {AxiosPromise} | ||
16 | + */ | ||
17 | +export function apiDelMessage(params) { | ||
18 | + return axios.get("/message/del", params); | ||
19 | +} | ||
20 | + | ||
21 | +/** | ||
22 | + * 删除回复 | ||
23 | + * @param data | ||
24 | + * @returns {AxiosPromise} | ||
25 | + */ | ||
26 | +export function apiDelReply(params) { | ||
27 | + return axios.postFile("/message/delReply", params); | ||
28 | +} |
rf-blog/code/admin/src/api/upload.js
0 → 100644
1 | +import axios from "utils/request"; | ||
2 | + | ||
3 | +/** | ||
4 | + * 上传图片 | ||
5 | + * @param data | ||
6 | + * @returns {AxiosPromise} | ||
7 | + */ | ||
8 | +export function apiUploadImg(params) { | ||
9 | + return axios.postFile("/uploadImage", params); | ||
10 | +} | ||
11 | +/** | ||
12 | + * 删除图片 | ||
13 | + * @param data | ||
14 | + * @returns {AxiosPromise} | ||
15 | + */ | ||
16 | +export function apiDelUploadImg(params) { | ||
17 | + return axios.post("/delUploadImage", params); | ||
18 | +} |
343 Bytes
1 | +<template> | ||
2 | + <div id="markdowm"> | ||
3 | + <div class="md-title"> | ||
4 | + <ul class="cf"> | ||
5 | + <li> | ||
6 | + <span>图片</span> | ||
7 | + <input type="file" class="uploadFile" @change="insertImg" /> | ||
8 | + </li> | ||
9 | + <li @click="insertCode"> | ||
10 | + <span>代码块</span> | ||
11 | + </li> | ||
12 | + <li @click="setCursorPosition($refs.text, '***')"> | ||
13 | + <span>分割线</span> | ||
14 | + </li> | ||
15 | + <li @click="setCursorPosition($refs.text, '****', 2)"> | ||
16 | + <span>粗体</span> | ||
17 | + </li> | ||
18 | + <li @click="setCursorPosition($refs.text, '**', 1)"> | ||
19 | + <span>斜体</span> | ||
20 | + </li> | ||
21 | + <li @click="setCursorPosition($refs.text, '> ', 2)"> | ||
22 | + <span>引用</span> | ||
23 | + </li> | ||
24 | + </ul> | ||
25 | + </div> | ||
26 | + <textarea v-model="val" ref="text" @keydown.tab="tabMarkdown"></textarea> | ||
27 | + <div class="render fmt" v-html="renderHtml"></div> | ||
28 | + </div> | ||
29 | +</template> | ||
30 | + | ||
31 | +<script> | ||
32 | +import marked from "marked"; | ||
33 | +import highlightJs from "highlight.js"; | ||
34 | +import { apiUploadImg } from "src/api/upload"; | ||
35 | +export default { | ||
36 | + props: ["value"], | ||
37 | + computed: { | ||
38 | + renderHtml() { | ||
39 | + marked.setOptions({ | ||
40 | + renderer: new marked.Renderer(), | ||
41 | + gfm: true, //允许 Git Hub标准的markdown. | ||
42 | + tables: true, //允许支持表格语法。该选项要求 gfm 为true。 | ||
43 | + breaks: true, //允许回车换行。该选项要求 gfm 为true。 | ||
44 | + pedantic: false, //尽可能地兼容 markdown.pl的晦涩部分。不纠正原始模型任何的不良行为和错误。 | ||
45 | + sanitize: true, //对输出进行过滤(清理),将忽略任何已经输入的html代码(标签) | ||
46 | + smartLists: true, //使用比原生markdown更时髦的列表。 旧的列表将可能被作为pedantic的处理内容过滤掉. | ||
47 | + smartypants: false, //使用更为时髦的标点,比如在引用语法中加入破折号。 | ||
48 | + highlight: function (code) { | ||
49 | + return highlightJs.highlightAuto(code).value; | ||
50 | + }, | ||
51 | + }); | ||
52 | + return marked(this.val); | ||
53 | + }, | ||
54 | + }, | ||
55 | + watch: { | ||
56 | + val(newVal) { | ||
57 | + this.handleModelInput(newVal); | ||
58 | + }, | ||
59 | + }, | ||
60 | + data() { | ||
61 | + return { | ||
62 | + val: this.value, | ||
63 | + link: "", | ||
64 | + textarea: null, | ||
65 | + }; | ||
66 | + }, | ||
67 | + mounted() { | ||
68 | + this.textarea = this.$refs.text; | ||
69 | + }, | ||
70 | + methods: { | ||
71 | + handleModelInput(newVal) { | ||
72 | + this.$emit("input", newVal); | ||
73 | + }, | ||
74 | + tabMarkdown(e) { | ||
75 | + // tab键 | ||
76 | + e.preventDefault(); | ||
77 | + let indent = " "; | ||
78 | + let start = this.textarea.selectionStart; | ||
79 | + let end = this.textarea.selectionEnd; | ||
80 | + let selected = window.getSelection().toString(); | ||
81 | + selected = indent + selected.replace(/\n/g, "\n" + indent); | ||
82 | + this.textarea.value = | ||
83 | + this.textarea.value.substring(0, start) + | ||
84 | + selected + | ||
85 | + this.textarea.value.substring(end); | ||
86 | + this.textarea.setSelectionRange( | ||
87 | + start + indent.length, | ||
88 | + start + selected.length | ||
89 | + ); | ||
90 | + }, | ||
91 | + insertImg(e) { | ||
92 | + // 插入图片 | ||
93 | + let formData = new FormData(), | ||
94 | + img = ""; | ||
95 | + formData.append("markdown_img", e.target.files[0]); | ||
96 | + return apiUploadImg(formData) | ||
97 | + .then((res) => { | ||
98 | + img = res.data.markdown_img; | ||
99 | + let val = `![图片描述](${img})`; | ||
100 | + this.setCursorPosition(this.$refs.text, val, 6); | ||
101 | + }) | ||
102 | + .catch((err) => { | ||
103 | + console.log(err); | ||
104 | + }) | ||
105 | + .finally(() => {}); | ||
106 | + }, | ||
107 | + insertCode() { | ||
108 | + let val = ` | ||
109 | +\`\`\` | ||
110 | + | ||
111 | +\`\`\``; | ||
112 | + this.setCursorPosition(this.$refs.text, val, val.length - 8); | ||
113 | + }, | ||
114 | + setCursorPosition(dom, val, posLen) { | ||
115 | + // 设置光标位置 | ||
116 | + var cursorPosition = 0; | ||
117 | + if (dom.selectionStart) { | ||
118 | + cursorPosition = dom.selectionStart; | ||
119 | + } | ||
120 | + this.insertAtCursor(dom, val); | ||
121 | + dom.focus(); | ||
122 | + dom.setSelectionRange( | ||
123 | + dom.value.length, | ||
124 | + cursorPosition + (posLen || val.length) | ||
125 | + ); | ||
126 | + this.val = dom.value; | ||
127 | + }, | ||
128 | + insertAtCursor(dom, val) { | ||
129 | + // 光标所在位置插入字符 | ||
130 | + if (document.selection) { | ||
131 | + dom.focus(); | ||
132 | + sel = document.selection.createRange(); | ||
133 | + sel.text = val; | ||
134 | + sel.select(); | ||
135 | + } else if (dom.selectionStart || dom.selectionStart == "0") { | ||
136 | + let startPos = dom.selectionStart; | ||
137 | + let endPos = dom.selectionEnd; | ||
138 | + let restoreTop = dom.scrollTop; | ||
139 | + dom.value = | ||
140 | + dom.value.substring(0, startPos) + | ||
141 | + val + | ||
142 | + dom.value.substring(endPos, dom.value.length); | ||
143 | + if (restoreTop > 0) { | ||
144 | + dom.scrollTop = restoreTop; | ||
145 | + } | ||
146 | + dom.focus(); | ||
147 | + dom.selectionStart = startPos + val.length; | ||
148 | + dom.selectionEnd = startPos + val.length; | ||
149 | + } else { | ||
150 | + dom.value += val; | ||
151 | + dom.focus(); | ||
152 | + } | ||
153 | + }, | ||
154 | + }, | ||
155 | +}; | ||
156 | +</script> | ||
157 | + | ||
158 | +<style lang="less" scoped> | ||
159 | +@import "./markdown.less"; | ||
160 | +@import "../../../../../node_modules/highlight.js/styles/tomorrow-night-eighties.css"; | ||
161 | +@md-bd-color: #dcdfe6; | ||
162 | +@md-title-color: rgb(233, 234, 237); | ||
163 | +@md-bg-color: #fff; | ||
164 | +@btn-hover: #3b7cff; | ||
165 | +#markdowm { | ||
166 | + width: 100%; | ||
167 | + height: 500px; | ||
168 | + text-align: left; | ||
169 | + overflow: hidden; | ||
170 | + border: 1px solid @md-bd-color; | ||
171 | + position: relative; | ||
172 | + .md-title { | ||
173 | + width: 100%; | ||
174 | + height: 40px; | ||
175 | + border-bottom: 1px solid #dcdfe6; | ||
176 | + background: @md-title-color; | ||
177 | + position: absolute; | ||
178 | + left: 0; | ||
179 | + top: 0; | ||
180 | + z-index: 99; | ||
181 | + li { | ||
182 | + width: 100px; | ||
183 | + height: 100%; | ||
184 | + text-align: center; | ||
185 | + position: relative; | ||
186 | + float: left; | ||
187 | + cursor: pointer; | ||
188 | + color: #606266; | ||
189 | + &:hover { | ||
190 | + color: @btn-hover; | ||
191 | + } | ||
192 | + &:after { | ||
193 | + content: ""; | ||
194 | + position: absolute; | ||
195 | + left: 100%; | ||
196 | + top: 50%; | ||
197 | + transform: translateY(-50%); | ||
198 | + width: 1px; | ||
199 | + height: 20px; | ||
200 | + background: @borderBoldColor; | ||
201 | + } | ||
202 | + &:last-child::after { | ||
203 | + content: none; | ||
204 | + } | ||
205 | + .uploadFile { | ||
206 | + position: absolute; | ||
207 | + top: 0; | ||
208 | + left: 0; | ||
209 | + width: 100%; | ||
210 | + height: 100%; | ||
211 | + opacity: 0; | ||
212 | + cursor: pointer; | ||
213 | + } | ||
214 | + } | ||
215 | + } | ||
216 | + textarea, | ||
217 | + .render { | ||
218 | + float: left; | ||
219 | + width: 50%; | ||
220 | + height: 100%; | ||
221 | + vertical-align: top; | ||
222 | + box-sizing: border-box; | ||
223 | + line-height: 22px; | ||
224 | + padding: 0 20px; | ||
225 | + } | ||
226 | + textarea { | ||
227 | + border: none; | ||
228 | + border-right: 1px solid @md-bd-color; | ||
229 | + resize: none; | ||
230 | + outline: none; | ||
231 | + background-color: @md-bg-color; | ||
232 | + color: @mainColor; | ||
233 | + font-size: 14px; | ||
234 | + line-height: 22px; | ||
235 | + padding: 20px; | ||
236 | + padding-top: 50px; | ||
237 | + } | ||
238 | + .render { | ||
239 | + background-color: @md-title-color; | ||
240 | + overflow-y: scroll; | ||
241 | + padding-top: 50px; | ||
242 | + } | ||
243 | + | ||
244 | + .mask { | ||
245 | + position: fixed; | ||
246 | + left: 0; | ||
247 | + top: 0; | ||
248 | + width: 100%; | ||
249 | + height: 100%; | ||
250 | + background: rgba(0, 0, 0, 0.5); | ||
251 | + z-index: 10; | ||
252 | + } | ||
253 | + .link-text { | ||
254 | + width: 500px; | ||
255 | + text-align: center; | ||
256 | + position: absolute; | ||
257 | + left: 50%; | ||
258 | + top: 50%; | ||
259 | + transform: translate(-50%, -50%); | ||
260 | + .link-input { | ||
261 | + width: 400px; | ||
262 | + } | ||
263 | + } | ||
264 | +} | ||
265 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +.fmt { | ||
2 | + line-height: 1.6; | ||
3 | + word-wrap: break-word; | ||
4 | + color: @mainColor; | ||
5 | +} | ||
6 | +.fmt a { | ||
7 | + color: #009a61; | ||
8 | + text-decoration: none; | ||
9 | +} | ||
10 | +.fmt h1 { | ||
11 | + font-size: 2.25em; | ||
12 | +} | ||
13 | + | ||
14 | +.fmt h2 { | ||
15 | + font-size: 1.75em; | ||
16 | +} | ||
17 | + | ||
18 | +.fmt h3 { | ||
19 | + font-size: 1.5em; | ||
20 | +} | ||
21 | + | ||
22 | +.fmt h4 { | ||
23 | + font-size: 1.25em; | ||
24 | +} | ||
25 | + | ||
26 | +.fmt h5 { | ||
27 | + font-size: 1em; | ||
28 | +} | ||
29 | + | ||
30 | +.fmt h6 { | ||
31 | + font-size: 0.86em; | ||
32 | +} | ||
33 | +.fmt p { | ||
34 | + margin-top: 0.86em; | ||
35 | + line-height: 1.8em; | ||
36 | +} | ||
37 | + | ||
38 | +.fmt h1, | ||
39 | +.fmt h2, | ||
40 | +.fmt h3, | ||
41 | +.fmt h4, | ||
42 | +.fmt h5, | ||
43 | +.fmt h6 { | ||
44 | + margin-top: 1.2em; | ||
45 | +} | ||
46 | + | ||
47 | +.fmt h1 + .widget-codetool + pre, | ||
48 | +.fmt h2 + .widget-codetool + pre, | ||
49 | +.fmt h3 + .widget-codetool + pre { | ||
50 | + margin-top: 1.2em !important; | ||
51 | +} | ||
52 | + | ||
53 | +.fmt h1, | ||
54 | +.fmt h2 { | ||
55 | + border-bottom: 1px solid #eee; | ||
56 | + padding-bottom: 10px; | ||
57 | +} | ||
58 | + | ||
59 | +.fmt > h1:first-child, | ||
60 | +.fmt h2:first-child, | ||
61 | +.fmt h3:first-child, | ||
62 | +.fmt h4:first-child, | ||
63 | +.fmt p:first-child, | ||
64 | +.fmt ul:first-child, | ||
65 | +.fmt ol:first-child, | ||
66 | +.fmt blockquote:first-child { | ||
67 | + margin-top: 0; | ||
68 | +} | ||
69 | + | ||
70 | +.fmt ul, | ||
71 | +.fmt ol { | ||
72 | + margin-left: 2em; | ||
73 | + margin-top: 0.86em; | ||
74 | + padding-left: 0; | ||
75 | +} | ||
76 | + | ||
77 | +.fmt ul li, | ||
78 | +.fmt ol li { | ||
79 | + margin: 0.3em 0; | ||
80 | + list-style: unset; | ||
81 | +} | ||
82 | + | ||
83 | +.fmt ul ul, | ||
84 | +.fmt ul ol, | ||
85 | +.fmt ol ul, | ||
86 | +.fmt ol ol { | ||
87 | + margin-top: 0; | ||
88 | + margin-bottom: 0; | ||
89 | +} | ||
90 | + | ||
91 | +.fmt ul p, | ||
92 | +.fmt ol p { | ||
93 | + margin: 0; | ||
94 | +} | ||
95 | + | ||
96 | +.fmt p:last-child { | ||
97 | + margin-bottom: 0; | ||
98 | +} | ||
99 | + | ||
100 | +.fmt p > p:empty, | ||
101 | +.fmt div > p:empty, | ||
102 | +.fmt p > div:empty, | ||
103 | +.fmt div > div:empty, | ||
104 | +.fmt div > br:only-child, | ||
105 | +.fmt p + br, | ||
106 | +.fmt img + br { | ||
107 | + display: none; | ||
108 | +} | ||
109 | + | ||
110 | +.fmt img, | ||
111 | +.fmt video, | ||
112 | +.fmt audio { | ||
113 | + position: static !important; | ||
114 | + max-width: 100%; | ||
115 | +} | ||
116 | + | ||
117 | +.fmt img { | ||
118 | + padding: 3px; | ||
119 | + border: 1px solid #ddd; | ||
120 | +} | ||
121 | + | ||
122 | +.fmt img.emoji { | ||
123 | + padding: 0; | ||
124 | + border: none; | ||
125 | +} | ||
126 | + | ||
127 | +.fmt blockquote { | ||
128 | + border-left: 2px solid #009a61; | ||
129 | + background: @thinBgColor; | ||
130 | + color: @thinColor; | ||
131 | + font-size: 1em; | ||
132 | +} | ||
133 | + | ||
134 | +.fmt pre, | ||
135 | +.fmt code { | ||
136 | + font-size: 0.93em; | ||
137 | + margin-top: 0.86em; | ||
138 | +} | ||
139 | + | ||
140 | +.fmt pre { | ||
141 | + font-family: "Source Code Pro", Consolas, Menlo, Monaco, "Courier New", | ||
142 | + monospace; | ||
143 | + padding: 1em; | ||
144 | + margin-top: 0.86em; | ||
145 | + border: none; | ||
146 | + overflow: auto; | ||
147 | + line-height: 1.45; | ||
148 | + max-height: 35em; | ||
149 | + position: relative; | ||
150 | + background: url("./blueprint.png") @thinBgColor; | ||
151 | + background-size: 30px, 30px; | ||
152 | + font-size: 12px; | ||
153 | + -webkit-overflow-scrolling: touch; | ||
154 | + border-radius: 5px; | ||
155 | +} | ||
156 | + | ||
157 | +.fmt pre code { | ||
158 | + background: none; | ||
159 | + font-size: 1em; | ||
160 | + overflow-wrap: normal; | ||
161 | + white-space: inherit; | ||
162 | +} | ||
163 | + | ||
164 | +.fmt hr { | ||
165 | + margin: 1.5em auto; | ||
166 | + border-top: 2px dotted #eee; | ||
167 | +} | ||
168 | + | ||
169 | +.fmt kbd { | ||
170 | + margin: 0 4px; | ||
171 | + padding: 3px 4px; | ||
172 | + background: #eee; | ||
173 | + color: @thinColor; | ||
174 | +} | ||
175 | + | ||
176 | +.fmt .x-scroll { | ||
177 | + overflow-x: auto; | ||
178 | +} | ||
179 | + | ||
180 | +.fmt table { | ||
181 | + width: 100%; | ||
182 | +} | ||
183 | + | ||
184 | +.fmt table th, | ||
185 | +.fmt table td { | ||
186 | + border: 1px solid #e6e6e6; | ||
187 | + padding: 5px 8px; | ||
188 | + word-break: normal; | ||
189 | +} | ||
190 | + | ||
191 | +.fmt table th { | ||
192 | + background: #f3f3f3; | ||
193 | +} | ||
194 | + | ||
195 | +.fmt a:not(.btn) { | ||
196 | + border-bottom: 1px solid rgba(0, 154, 97, 0.25); | ||
197 | + padding-bottom: 1px; | ||
198 | +} | ||
199 | + | ||
200 | +.fmt a:not(.btn):hover { | ||
201 | + border-bottom: 1px solid #009a61; | ||
202 | + text-decoration: none; | ||
203 | +} | ||
204 | + | ||
205 | +.hljs { | ||
206 | + display: block; | ||
207 | + overflow-x: auto; | ||
208 | + padding: 0.5em; | ||
209 | + color: @mainColor; | ||
210 | + background: #f8f8f8; | ||
211 | +} | ||
212 | + | ||
213 | +.hljs-comment, | ||
214 | +.hljs-quote { | ||
215 | + color: #998; | ||
216 | + font-style: italic; | ||
217 | +} | ||
218 | + | ||
219 | +.hljs-keyword, | ||
220 | +.hljs-selector-tag, | ||
221 | +.hljs-subst { | ||
222 | + color: @mainColor; | ||
223 | + font-weight: bold; | ||
224 | +} | ||
225 | + | ||
226 | +.hljs-number, | ||
227 | +.hljs-literal, | ||
228 | +.hljs-variable, | ||
229 | +.hljs-template-variable, | ||
230 | +.hljs-tag .hljs-attr { | ||
231 | + color: #008080; | ||
232 | +} | ||
233 | + | ||
234 | +.hljs-string, | ||
235 | +.hljs-doctag { | ||
236 | + color: #d14; | ||
237 | +} | ||
238 | + | ||
239 | +.hljs-title, | ||
240 | +.hljs-section, | ||
241 | +.hljs-selector-id { | ||
242 | + color: #900; | ||
243 | + font-weight: bold; | ||
244 | +} | ||
245 | + | ||
246 | +.hljs-subst { | ||
247 | + font-weight: normal; | ||
248 | +} | ||
249 | + | ||
250 | +.hljs-type, | ||
251 | +.hljs-class .hljs-title { | ||
252 | + color: #458; | ||
253 | + font-weight: bold; | ||
254 | +} | ||
255 | + | ||
256 | +.hljs-tag, | ||
257 | +.hljs-name, | ||
258 | +.hljs-attribute { | ||
259 | + color: #000080; | ||
260 | + font-weight: normal; | ||
261 | +} | ||
262 | + | ||
263 | +.hljs-regexp, | ||
264 | +.hljs-link { | ||
265 | + color: #009926; | ||
266 | +} | ||
267 | + | ||
268 | +.hljs-symbol, | ||
269 | +.hljs-bullet { | ||
270 | + color: #990073; | ||
271 | +} | ||
272 | + | ||
273 | +.hljs-built_in, | ||
274 | +.hljs-builtin-name { | ||
275 | + color: #0086b3; | ||
276 | +} | ||
277 | + | ||
278 | +.hljs-meta { | ||
279 | + color: @assistColor; | ||
280 | + font-weight: bold; | ||
281 | +} | ||
282 | + | ||
283 | +.hljs-deletion { | ||
284 | + background: #fdd; | ||
285 | +} | ||
286 | + | ||
287 | +.hljs-addition { | ||
288 | + background: #dfd; | ||
289 | +} | ||
290 | + | ||
291 | +.hljs-emphasis { | ||
292 | + font-style: italic; | ||
293 | +} | ||
294 | + | ||
295 | +.hljs-strong { | ||
296 | + font-weight: bold; | ||
297 | +} |
1 | +/** | ||
2 | + * 注入公共组件 | ||
3 | + */ | ||
4 | +import Vue from "vue"; | ||
5 | +// 检索当前目录的vue文件,便检索子文件夹 | ||
6 | +const componentsContext = require.context("./", true, /.vue$/); | ||
7 | +componentsContext.keys().forEach((component) => { | ||
8 | + // 获取文件中的 default 模块 | ||
9 | + const componentConfig = componentsContext(component).default; | ||
10 | + componentConfig.name && Vue.component(componentConfig.name, componentConfig); | ||
11 | +}); |
1 | +<template> | ||
2 | + <zp-page> | ||
3 | + <!-- 页面标题 --> | ||
4 | + <div slot="header"> | ||
5 | + <zp-page-header :back="back" @back="$emit('back')"> | ||
6 | + <slot name="header">{{ header }}</slot> | ||
7 | + </zp-page-header> | ||
8 | + </div> | ||
9 | + | ||
10 | + <!-- 主体body --> | ||
11 | + <div class="zp-page-edit"> | ||
12 | + <slot></slot> | ||
13 | + | ||
14 | + <!-- 保存等操作按钮 --> | ||
15 | + <div class="zp-page-edit-button" v-if="this.$slots.button"> | ||
16 | + <slot name="button"></slot> | ||
17 | + </div> | ||
18 | + </div> | ||
19 | + </zp-page> | ||
20 | +</template> | ||
21 | +<script> | ||
22 | +import zpPage from "./zp-page"; | ||
23 | +import zpPageHeader from "./zp-page-header"; | ||
24 | + | ||
25 | +export default { | ||
26 | + name: "zpPageEdit", | ||
27 | + components: { | ||
28 | + zpPage, | ||
29 | + zpPageHeader, | ||
30 | + }, | ||
31 | + props: { | ||
32 | + header: { | ||
33 | + type: String, | ||
34 | + default: "", | ||
35 | + }, | ||
36 | + back: { | ||
37 | + type: Boolean, | ||
38 | + default: true, | ||
39 | + }, | ||
40 | + }, | ||
41 | + created() {}, | ||
42 | + methods: {}, | ||
43 | +}; | ||
44 | +</script> | ||
45 | +<style lang="less" scope> | ||
46 | +.zp-page-edit { | ||
47 | + .el-form { | ||
48 | + margin-top: 20px; | ||
49 | + } | ||
50 | + .el-input { | ||
51 | + width: 220px; | ||
52 | + } | ||
53 | + .el-textarea { | ||
54 | + width: 440px; | ||
55 | + .el-textarea__inner { | ||
56 | + min-height: 100px !important; | ||
57 | + } | ||
58 | + } | ||
59 | + .zp-notice-subtitle { | ||
60 | + width: 540px; | ||
61 | + box-sizing: border-box; | ||
62 | + } | ||
63 | +} | ||
64 | +.zp-page-edit-button { | ||
65 | + padding: 20px 20px 20px 120px; | ||
66 | +} | ||
67 | +</style> |
1 | +<template> | ||
2 | + <div class="zp-page-filter"> | ||
3 | + <el-form ref="pageFilter" :inline="true" :label-width="labelWidth + 'px'"> | ||
4 | + <el-form-item | ||
5 | + v-for="(searchItem, searchIndex) in searchForm" | ||
6 | + :key="searchIndex" | ||
7 | + :label="searchItem.label" | ||
8 | + > | ||
9 | + <!-- 输入框 --> | ||
10 | + <el-input | ||
11 | + v-if="searchItem.type == 'text'" | ||
12 | + v-model="searchItem.value" | ||
13 | + :placeholder="searchItem.placeholder || '请输入' + searchItem.label" | ||
14 | + /> | ||
15 | + </el-form-item> | ||
16 | + </el-form> | ||
17 | + </div> | ||
18 | +</template> | ||
19 | +<script> | ||
20 | +export default { | ||
21 | + name: "zpPageFilter", | ||
22 | + components: {}, | ||
23 | + props: { | ||
24 | + labelWidth: { | ||
25 | + type: Number, | ||
26 | + default: 100, | ||
27 | + }, | ||
28 | + searchForm: { | ||
29 | + type: Array, | ||
30 | + default: () => [], | ||
31 | + }, | ||
32 | + }, | ||
33 | + data() { | ||
34 | + return {}; | ||
35 | + }, | ||
36 | + watch: {}, | ||
37 | + methods: {}, | ||
38 | +}; | ||
39 | +</script> | ||
40 | +<style lang="less" scope></style> |
1 | +<!-- | ||
2 | + * @Descripttion: | ||
3 | + * @Author: givon.chen | ||
4 | + * @Date: 2020-06-02 14:48:49 | ||
5 | + * @LastEditTime: 2020-06-30 15:41:59 | ||
6 | +--> | ||
7 | +<template> | ||
8 | + <div class="zp-page-header"> | ||
9 | + <!-- 带返回 --> | ||
10 | + <el-page-header | ||
11 | + :class="{ hideBack: !back }" | ||
12 | + @back="$emit('back')" | ||
13 | + :title="title" | ||
14 | + > | ||
15 | + <template v-slot:content> | ||
16 | + <slot> | ||
17 | + {{ header }} | ||
18 | + </slot> | ||
19 | + </template> | ||
20 | + </el-page-header> | ||
21 | + </div> | ||
22 | +</template> | ||
23 | + | ||
24 | +<script> | ||
25 | +export default { | ||
26 | + name: "zpPageHeader", | ||
27 | + props: { | ||
28 | + header: { | ||
29 | + type: String, | ||
30 | + default: "", | ||
31 | + }, | ||
32 | + back: { | ||
33 | + type: Boolean, | ||
34 | + default: false, | ||
35 | + }, | ||
36 | + title: { | ||
37 | + type: String, | ||
38 | + default: "返回", | ||
39 | + }, | ||
40 | + }, | ||
41 | +}; | ||
42 | +</script> | ||
43 | + | ||
44 | +<style lang="less" scope> | ||
45 | +.zp-page-header { | ||
46 | + display: flex; | ||
47 | + align-items: center; | ||
48 | + height: 54px; | ||
49 | + font-weight: bold; | ||
50 | + | ||
51 | + .el-page-header { | ||
52 | + flex-grow: 1; | ||
53 | + padding-left: 0; | ||
54 | + box-shadow: none; | ||
55 | + line-height: 28px !important; | ||
56 | + | ||
57 | + .el-page-header__content { | ||
58 | + // header 里 tabs的情形 | ||
59 | + .el-tabs { | ||
60 | + .el-tabs__header { | ||
61 | + margin: 0; | ||
62 | + .el-tabs__nav-wrap { | ||
63 | + padding-left: 0; | ||
64 | + } | ||
65 | + } | ||
66 | + } | ||
67 | + } | ||
68 | + } | ||
69 | + .hideBack { | ||
70 | + .el-page-header__left { | ||
71 | + display: none; | ||
72 | + } | ||
73 | + } | ||
74 | +} | ||
75 | +</style> |
1 | +<!-- | ||
2 | + * @Descripttion: | ||
3 | + * @Author: givon.chen | ||
4 | + * @Date: 2020-05-07 21:05:06 | ||
5 | + * @LastEditTime: 2020-06-29 12:09:31 | ||
6 | +--> | ||
7 | +<template> | ||
8 | + <zp-page> | ||
9 | + <!-- 页面标题 --> | ||
10 | + <template v-slot:header v-if="!hideHeader"> | ||
11 | + <zp-page-header :back="back" @back="back && $emit('back')"> | ||
12 | + <slot name="header">{{ header }}</slot> | ||
13 | + </zp-page-header> | ||
14 | + </template> | ||
15 | + | ||
16 | + <!-- 搜索框 --> | ||
17 | + <div class="zp-page-filter" v-if="this.$slots['filter']"> | ||
18 | + <slot name="filter"></slot> | ||
19 | + </div> | ||
20 | + | ||
21 | + <!-- 搜索框按钮组 --> | ||
22 | + <div class="zp-search-button" v-if="this.$slots['button']"> | ||
23 | + <slot name="button"></slot> | ||
24 | + </div> | ||
25 | + | ||
26 | + <!-- 列表、分页 --> | ||
27 | + <div class="zp-page-list"> | ||
28 | + <slot name="list"></slot> | ||
29 | + </div> | ||
30 | + </zp-page> | ||
31 | +</template> | ||
32 | +<script> | ||
33 | +import zpPage from "./zp-page"; | ||
34 | +import zpPageHeader from "./zp-page-header"; | ||
35 | + | ||
36 | +export default { | ||
37 | + name: "zpPageList", | ||
38 | + components: { | ||
39 | + zpPage, | ||
40 | + zpPageHeader, | ||
41 | + }, | ||
42 | + props: { | ||
43 | + back: { | ||
44 | + type: Boolean, | ||
45 | + default: false, | ||
46 | + }, | ||
47 | + header: { | ||
48 | + type: String, | ||
49 | + default: "", | ||
50 | + }, | ||
51 | + hideHeader: { | ||
52 | + type: Boolean, | ||
53 | + default: false, | ||
54 | + }, | ||
55 | + }, | ||
56 | + created() {}, | ||
57 | + methods: {}, | ||
58 | +}; | ||
59 | +</script> | ||
60 | +<style lang="less" scope> | ||
61 | +.zp-search-button { | ||
62 | + padding-bottom: 20px; | ||
63 | + padding-top: 0; | ||
64 | + .el-button { | ||
65 | + border-radius: 1px; | ||
66 | + width: 100px !important; | ||
67 | + } | ||
68 | +} | ||
69 | +.el-table { | ||
70 | + th { | ||
71 | + font-size: 14px; | ||
72 | + } | ||
73 | + td { | ||
74 | + font-size: 13px; | ||
75 | + } | ||
76 | + .el-button--text { | ||
77 | + padding-top: 0; | ||
78 | + padding-bottom: 0; | ||
79 | + height: 18px; | ||
80 | + line-height: 18px; | ||
81 | + font-size: 13px; | ||
82 | + border: 0 none; | ||
83 | + span { | ||
84 | + vertical-align: top; | ||
85 | + display: inline-block; | ||
86 | + line-height: 18px; | ||
87 | + } | ||
88 | + } | ||
89 | +} | ||
90 | +.el-pagination { | ||
91 | + padding-bottom: 0 !important; | ||
92 | + margin-top: 24px; | ||
93 | +} | ||
94 | +.el-pagination__sizes { | ||
95 | + .el-select { | ||
96 | + .el-input { | ||
97 | + .el-input__inner { | ||
98 | + padding-left: 0 !important; | ||
99 | + } | ||
100 | + } | ||
101 | + } | ||
102 | +} | ||
103 | +</style> |
1 | +<!-- | ||
2 | + * @Descripttion: | ||
3 | + * @Author: givon.chen | ||
4 | + * @Date: 2020-05-15 12:48:29 | ||
5 | + * @LastEditTime: 2020-06-30 15:43:16 | ||
6 | +--> | ||
7 | +<template> | ||
8 | + <div class="zp-page-container"> | ||
9 | + <el-card class="zp-page-el-card" shadow="never"> | ||
10 | + <template v-slot:header class="clearfix" v-if="this.$slots.header"> | ||
11 | + <slot name="header"></slot> | ||
12 | + </template> | ||
13 | + | ||
14 | + <div class="zp-page-body"> | ||
15 | + <slot></slot> | ||
16 | + </div> | ||
17 | + </el-card> | ||
18 | + </div> | ||
19 | +</template> | ||
20 | +<script> | ||
21 | +export default { | ||
22 | + name: "zpPage", | ||
23 | + created() {}, | ||
24 | + methods: {}, | ||
25 | +}; | ||
26 | +</script> | ||
27 | +<style lang="less" scope> | ||
28 | +.zp-page-container { | ||
29 | + background-color: #fff; | ||
30 | + .zp-page-el-card { | ||
31 | + border-radius: 0; | ||
32 | + border: 0 none; | ||
33 | + | ||
34 | + .el-card__header { | ||
35 | + font-size: 16px; | ||
36 | + font-weight: bold; | ||
37 | + color: $strongFontColor; | ||
38 | + background: #ffffff; | ||
39 | + padding: 0px 20px !important; | ||
40 | + line-height: 28px; | ||
41 | + } | ||
42 | + } | ||
43 | + .el-icon-edit { | ||
44 | + position: relative; | ||
45 | + width: 16px; | ||
46 | + height: 16px; | ||
47 | + display: inline-block !important; | ||
48 | + vertical-align: middle !important; | ||
49 | + padding: 10px; | ||
50 | + cursor: pointer; | ||
51 | + &:before { | ||
52 | + position: absolute; | ||
53 | + left: 10px; | ||
54 | + top: 10px; | ||
55 | + content: "" !important; | ||
56 | + width: 16px; | ||
57 | + height: 16px; | ||
58 | + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAByUExURUxpcTx//z1//zt9/zx8/zt9/zt8/z9//zt8/2aZ/zt8/z1//z59/z19/zx9/zx8/z9//z1//zt9/zx//zt//zt8/zt8/zx9/zt8/zt8/zt8/zt8/zt8/zt8/zt8/zt8/z9//z9//zyH/zx8/zt8/zt8/4mnP5MAAAAldFJOUwAuQoqu0usU0QW9MjVPO0wwPkVIOPf4rfXw8/bt8vnvCAwRx4muFNzGAAAAtklEQVRIx+3Q2Q6DIBCF4SloUbvv+955/1dsK6kOpoTDlUnjuSTfj0Gibv+362YU5W8FnycR/v5g5vUCD5782WoOB3lSFgcFaaPzb7GdIT7lpCr2Y8SzKE5LxMtiN0S8LC4DxMviOA34TPXdogh4Qz2neB8EPDkF4mWhIS8KwjyRKt+rvT5reHuQmpY8/fbe/0kUeb8NIu63QcT9boB4GUBeBJivA+C9VVAv7CnWN77grK2gm38vXdQ1SLUwEaMAAAAASUVORK5CYII=") | ||
59 | + no-repeat; | ||
60 | + background-size: 16px; | ||
61 | + } | ||
62 | + } | ||
63 | +} | ||
64 | +</style> |
1 | +<!-- | ||
2 | +table 配置项: | ||
3 | +1. loading | ||
4 | +2. source: 数据list | ||
5 | +3. count: 数据总数 | ||
6 | +4. columns: 列配置 | ||
7 | + 同el-table-column 属性 | ||
8 | + 补充: | ||
9 | + 1. zpMark: 特殊几个字段固定宽度,具体查看 @zpAdmin/utils/config/styleConfig | ||
10 | + 2. slot: 自定义列名 | ||
11 | +5. leftFix: 左侧固定列栏数 | ||
12 | +6. rightFix: 右侧固定列栏数 | ||
13 | +7. pageable: 是否显示分页 | ||
14 | +8. pageSize: 默认每页条数 | ||
15 | +9. layout: 分页配置 | ||
16 | + | ||
17 | +事件: | ||
18 | +1. sizeChange: 分页切换 | 参数: { pageNo, pageSize } | ||
19 | +2. selectChange: 多选切换 | 参数: 同element table | ||
20 | + --> | ||
21 | +<template> | ||
22 | + <div class="zp-table-list"> | ||
23 | + <el-table | ||
24 | + ref="table" | ||
25 | + v-loading="loading" | ||
26 | + :stripe="stripe" | ||
27 | + :data="source" | ||
28 | + :row-key="rowKey" | ||
29 | + :show-header="showHeader" | ||
30 | + :empty-text="loading ? ' ' : '暂无数据'" | ||
31 | + @selection-change="handleSelectionChange" | ||
32 | + @sort-change="handleSortChange" | ||
33 | + > | ||
34 | + <template v-for="(column, columnKey) in columns"> | ||
35 | + <template v-if="column.type"> | ||
36 | + <template v-if="column.type === 'selection'"> | ||
37 | + <!-- 选择栏固定宽度 --> | ||
38 | + <el-table-column | ||
39 | + type="selection" | ||
40 | + width="55" | ||
41 | + :show-overflow-tooltip="column.showTooltip" | ||
42 | + :align="column.align" | ||
43 | + :key="columnKey" | ||
44 | + :reserve-selection="column.reserveSelection" | ||
45 | + :selectable=" | ||
46 | + column.selectable | ||
47 | + ? column.selectable | ||
48 | + : () => { | ||
49 | + return true; | ||
50 | + } | ||
51 | + " | ||
52 | + ></el-table-column> | ||
53 | + </template> | ||
54 | + </template> | ||
55 | + | ||
56 | + <template v-else> | ||
57 | + <el-table-column | ||
58 | + :type="column.type" | ||
59 | + :key="column.prop" | ||
60 | + :prop="column.prop" | ||
61 | + :label="column.label" | ||
62 | + :width="getWidth(column)" | ||
63 | + :fixed="getFixed(columnKey)" | ||
64 | + :class-name="column.className" | ||
65 | + :min-width="column.minWidth" | ||
66 | + :render-header="column.renderHeader" | ||
67 | + :show-overflow-tooltip="column.showTooltip" | ||
68 | + :align="column.align" | ||
69 | + :header-align="column.headerAlign" | ||
70 | + :label-class-name="column.labelClassName" | ||
71 | + :formatter="column.formatter" | ||
72 | + :sortable="column.sortable || false" | ||
73 | + > | ||
74 | + <template v-if="column.slotHeader" slot="header"> | ||
75 | + <slot :name="column.slotHeader"></slot> | ||
76 | + </template> | ||
77 | + <template slot-scope="scope"> | ||
78 | + <!-- 自定义列: 比如操作列 start --> | ||
79 | + <slot | ||
80 | + v-if="column.slot" | ||
81 | + :name="column.slot" | ||
82 | + :$index="scope.$index" | ||
83 | + :column="scope.column" | ||
84 | + :row="scope.row" | ||
85 | + ></slot> | ||
86 | + <!-- 自定义列: 比如操作列 end --> | ||
87 | + <template v-else> | ||
88 | + <template v-if="column.formatter"> | ||
89 | + {{ column.formatter(scope.row, column) }} | ||
90 | + </template> | ||
91 | + | ||
92 | + <template v-else> | ||
93 | + {{ scope.row[column.prop] }} | ||
94 | + </template> | ||
95 | + </template> | ||
96 | + </template> | ||
97 | + </el-table-column> | ||
98 | + </template> | ||
99 | + </template> | ||
100 | + </el-table> | ||
101 | + <el-pagination | ||
102 | + v-if="pageable && count > 0" | ||
103 | + @size-change="(value) => handleSizeChange(value)" | ||
104 | + @current-change="(value) => handleCurrentChange(value)" | ||
105 | + :current-page="page" | ||
106 | + :page-sizes="pageSizes" | ||
107 | + :page-size="currentPageSize" | ||
108 | + :layout="pageLayout" | ||
109 | + :total="count" | ||
110 | + ></el-pagination> | ||
111 | + </div> | ||
112 | +</template> | ||
113 | +<script> | ||
114 | +import { tableFixWidths } from "src/utils/config/styleConfig"; | ||
115 | + | ||
116 | +export default { | ||
117 | + name: "zpTableList", | ||
118 | + data() { | ||
119 | + return { | ||
120 | + page: 1, | ||
121 | + currentPageSize: 20, | ||
122 | + pageSizes: [10, 20, 30, 50], | ||
123 | + pageLayout: "", | ||
124 | + }; | ||
125 | + }, | ||
126 | + props: { | ||
127 | + // 是否显示表头 | ||
128 | + showHeader: { | ||
129 | + type: Boolean, | ||
130 | + default: true, | ||
131 | + }, | ||
132 | + // 是否斑马纹 | ||
133 | + stripe: { | ||
134 | + type: Boolean, | ||
135 | + default: true, | ||
136 | + }, | ||
137 | + // 加载状态 | ||
138 | + loading: { | ||
139 | + type: Boolean, | ||
140 | + default: false, | ||
141 | + }, | ||
142 | + // 数据资源 | ||
143 | + source: { | ||
144 | + type: Array, | ||
145 | + default: () => [], | ||
146 | + }, | ||
147 | + // 列配置 | ||
148 | + columns: { | ||
149 | + type: Array, | ||
150 | + default: () => [], | ||
151 | + }, | ||
152 | + // 列表总数量 | ||
153 | + count: { | ||
154 | + type: Number, | ||
155 | + default: 0, | ||
156 | + }, | ||
157 | + // 左侧固定列栏数 | ||
158 | + leftFix: { | ||
159 | + type: Number, | ||
160 | + default: 1, | ||
161 | + }, | ||
162 | + // 右侧固定列栏数 | ||
163 | + rightFix: { | ||
164 | + type: Number, | ||
165 | + default: 1, | ||
166 | + }, | ||
167 | + // 是否分页 | ||
168 | + pageable: { | ||
169 | + type: Boolean, | ||
170 | + default: true, | ||
171 | + }, | ||
172 | + // 每页条数 | ||
173 | + pageSize: { | ||
174 | + type: Number, | ||
175 | + default: 10, | ||
176 | + }, | ||
177 | + // 分页组件布局 | ||
178 | + layout: { | ||
179 | + type: String, | ||
180 | + default: "total, sizes, prev, pager, next, jumper", | ||
181 | + }, | ||
182 | + rowKey: [String, Function], | ||
183 | + }, | ||
184 | + watch: { | ||
185 | + count: { | ||
186 | + handler(value) { | ||
187 | + // 解决查询列表,分页器页码没同步更新问题 | ||
188 | + if (value === 0) { | ||
189 | + this.page = 1; | ||
190 | + } | ||
191 | + }, | ||
192 | + }, | ||
193 | + pageSize: { | ||
194 | + handler(value) { | ||
195 | + value && (this.currentPageSize = value); | ||
196 | + }, | ||
197 | + immediate: true, | ||
198 | + }, | ||
199 | + layout: { | ||
200 | + handler(value) { | ||
201 | + value && (this.pageLayout = value); | ||
202 | + }, | ||
203 | + immediate: true, | ||
204 | + }, | ||
205 | + }, | ||
206 | + created() {}, | ||
207 | + methods: { | ||
208 | + doLayout() { | ||
209 | + this.$refs.table.doLayout(); | ||
210 | + }, | ||
211 | + handleSortChange(value) { | ||
212 | + console.log(value); | ||
213 | + this.$emit("sort-change", value); | ||
214 | + }, | ||
215 | + /** | ||
216 | + * 选择操作 | ||
217 | + * @param value | ||
218 | + */ | ||
219 | + handleSelectionChange(value) { | ||
220 | + this.$emit("selection-change", value); | ||
221 | + }, | ||
222 | + /** | ||
223 | + * 当前页码变化 | ||
224 | + * @param val | ||
225 | + */ | ||
226 | + handleCurrentChange(val) { | ||
227 | + this.page = val; | ||
228 | + this.$emit("current-change", val); | ||
229 | + }, | ||
230 | + /** | ||
231 | + * 每页数量变化 | ||
232 | + * @param val | ||
233 | + */ | ||
234 | + handleSizeChange(val) { | ||
235 | + this.currentPageSize = val; | ||
236 | + this.$emit("size-change", val); | ||
237 | + }, | ||
238 | + // 是否固定 | ||
239 | + getFixed(columnKey) { | ||
240 | + if (columnKey < this.leftFix) { | ||
241 | + return "left"; | ||
242 | + } else if (columnKey > this.columns.length - this.rightFix - 1) { | ||
243 | + return "right"; | ||
244 | + } | ||
245 | + return false; | ||
246 | + }, | ||
247 | + // 宽度 | ||
248 | + getWidth(column) { | ||
249 | + // 1. 优先 zpMark 固定宽度项 | ||
250 | + // 2. width属性 | ||
251 | + // 3. 不设宽度 | ||
252 | + if (column.zpMark) { | ||
253 | + if (Object.keys(tableFixWidths).includes(column.zpMark)) { | ||
254 | + return tableFixWidths[column.zpMark]; | ||
255 | + } | ||
256 | + } | ||
257 | + if (column.width) { | ||
258 | + return column.width; | ||
259 | + } | ||
260 | + return ""; | ||
261 | + }, | ||
262 | + }, | ||
263 | +}; | ||
264 | +</script> | ||
265 | +<style lang="less" scope> | ||
266 | +.zp-table-list { | ||
267 | + .el-table { | ||
268 | + .el-button--text { | ||
269 | + padding: 0 2px; | ||
270 | + } | ||
271 | + | ||
272 | + .el-tag { | ||
273 | + height: 24px; | ||
274 | + line-height: 22px; | ||
275 | + border-radius: 12px; | ||
276 | + } | ||
277 | + } | ||
278 | + | ||
279 | + .el-table__fixed, | ||
280 | + .el-table__fixed-right { | ||
281 | + &:before { | ||
282 | + background-color: transparent; | ||
283 | + } | ||
284 | + } | ||
285 | +} | ||
286 | +</style> | ||
287 | +<style lang="less" scoped> | ||
288 | +.zp-table-list { | ||
289 | + height: 100%; | ||
290 | + display: flex; | ||
291 | + flex-direction: column; | ||
292 | + | ||
293 | + .el-pagination { | ||
294 | + text-align: right; | ||
295 | + | ||
296 | + .el-pagination__total { | ||
297 | + float: left; | ||
298 | + } | ||
299 | + } | ||
300 | +} | ||
301 | +</style> |
1 | +<template> | ||
2 | + <svg class="icon" aria-hidden="true"> | ||
3 | + <use :xlink:href="iconName"></use> | ||
4 | + </svg> | ||
5 | +</template> | ||
6 | + | ||
7 | +<script> | ||
8 | +export default { | ||
9 | + name: "Icon", | ||
10 | + props: { | ||
11 | + name: { | ||
12 | + type: String, | ||
13 | + required: true, | ||
14 | + }, | ||
15 | + }, | ||
16 | + computed: { | ||
17 | + iconName() { | ||
18 | + return `#icon-${this.name}`; | ||
19 | + }, | ||
20 | + }, | ||
21 | +}; | ||
22 | +</script> |
1 | +<template> | ||
2 | + <el-dialog | ||
3 | + :title="title" | ||
4 | + class="zp-dialog__wrapper" | ||
5 | + v-bind="$attrs" | ||
6 | + v-on="$listeners" | ||
7 | + > | ||
8 | + <template v-slot:title v-if="this.$slots.title"> | ||
9 | + <slot name="title"></slot> | ||
10 | + </template> | ||
11 | + <template v-slot:footer> | ||
12 | + <slot name="footer"></slot> | ||
13 | + </template> | ||
14 | + <slot></slot> | ||
15 | + </el-dialog> | ||
16 | +</template> | ||
17 | + | ||
18 | +<script> | ||
19 | +export default { | ||
20 | + name: "ZpDialog", | ||
21 | + components: {}, | ||
22 | + props: { | ||
23 | + title: { | ||
24 | + type: String, | ||
25 | + default: "", | ||
26 | + }, | ||
27 | + }, | ||
28 | + computed: {}, | ||
29 | + data() { | ||
30 | + return {}; | ||
31 | + }, | ||
32 | + watch: {}, | ||
33 | + created() {}, | ||
34 | + mounted() {}, | ||
35 | + beforeDestroy() {}, | ||
36 | + methods: {}, | ||
37 | +}; | ||
38 | +</script> | ||
39 | + | ||
40 | +<style lang="less" scoped> | ||
41 | +.zp-dialog__wrapper { | ||
42 | + /deep/ .el-dialog { | ||
43 | + .el-dialog__body { | ||
44 | + box-sizing: border-box; | ||
45 | + max-height: calc(~"85vh - 112px"); | ||
46 | + overflow-y: auto; | ||
47 | + } | ||
48 | + } | ||
49 | +} | ||
50 | +</style> |
1 | +<template> | ||
2 | + <div class="zp-single-img-upload__container"> | ||
3 | + <el-upload | ||
4 | + ref="upload" | ||
5 | + :action="action" | ||
6 | + :accept="accept" | ||
7 | + :file-list="fileList" | ||
8 | + :headers="headers" | ||
9 | + :data="data" | ||
10 | + list-type="picture-card" | ||
11 | + :auto-upload="true" | ||
12 | + :on-change="onChange" | ||
13 | + :on-success="onSuccess" | ||
14 | + :before-upload="beforeUpload" | ||
15 | + > | ||
16 | + <div ref="triggerWrapper" class="zp-single-img-upload__trigger-wrapper"> | ||
17 | + <slot v-if="this.$slots['trigger']" name="trigger"></slot> | ||
18 | + <i v-else slot="default" class="el-icon-plus"></i> | ||
19 | + </div> | ||
20 | + | ||
21 | + <div | ||
22 | + class="zp-single-img-upload__img-wrapper" | ||
23 | + slot="file" | ||
24 | + slot-scope="{ file }" | ||
25 | + > | ||
26 | + <el-image | ||
27 | + ref="zpSingleImg" | ||
28 | + class="zp-single-img-upload__img" | ||
29 | + v-if="imgUrl" | ||
30 | + :src="imgUrl" | ||
31 | + :preview-src-list="[imgUrl]" | ||
32 | + fit="contain" | ||
33 | + ></el-image> | ||
34 | + <span class="el-upload-list__item-actions" v-if="preview"> | ||
35 | + <span | ||
36 | + class="el-upload-list__item-preview" | ||
37 | + @click="handlePictureCardPreview(file)" | ||
38 | + > | ||
39 | + <i class="el-icon-zoom-in"></i> | ||
40 | + </span> | ||
41 | + <span class="el-upload-list__item-delete" @click="handleDelete()"> | ||
42 | + <i class="el-icon-delete"></i> | ||
43 | + </span> | ||
44 | + </span> | ||
45 | + <div class="zp-single-img-upload__reload" @click="handleReload" v-else> | ||
46 | + 更换图片 | ||
47 | + </div> | ||
48 | + </div> | ||
49 | + <div class="el-upload__tip" slot="tip"> | ||
50 | + <slot v-if="this.$slots['tip']" name="tip"></slot> | ||
51 | + <div v-else>支持图片格式:jpg、png</div> | ||
52 | + </div> | ||
53 | + </el-upload> | ||
54 | + </div> | ||
55 | +</template> | ||
56 | + | ||
57 | +<script> | ||
58 | +import { getToken } from "utils/auth"; | ||
59 | + | ||
60 | +export default { | ||
61 | + name: "zpSingleImgUpload", | ||
62 | + props: { | ||
63 | + //图片url | ||
64 | + imgUrl: { | ||
65 | + type: String, | ||
66 | + default: "", | ||
67 | + }, | ||
68 | + //图片上传接口地址 | ||
69 | + action: { | ||
70 | + type: String, | ||
71 | + default: "/admin_api/uploadImage", | ||
72 | + }, | ||
73 | + //支持的图片格式 | ||
74 | + accept: { | ||
75 | + type: String, | ||
76 | + default: "image/*", | ||
77 | + }, | ||
78 | + //文件最大大小(M) | ||
79 | + maxSize: { | ||
80 | + type: Number, | ||
81 | + default: 6, | ||
82 | + }, | ||
83 | + //文件存储是否公有(公有存储读取的时候不需要签名,敏感文件私有,否则公有) | ||
84 | + public: { | ||
85 | + type: Boolean, | ||
86 | + default: false, | ||
87 | + }, | ||
88 | + //是否可以预览图片 | ||
89 | + preview: { | ||
90 | + type: Boolean, | ||
91 | + default: false, | ||
92 | + }, | ||
93 | + }, | ||
94 | + model: { | ||
95 | + prop: "imgUrl", | ||
96 | + event: "input", | ||
97 | + }, | ||
98 | + data() { | ||
99 | + return { | ||
100 | + headers: {}, //请求头 | ||
101 | + data: {}, //请求额外参数 | ||
102 | + fileList: [], //文件列表 | ||
103 | + limitCount: 1, //最多支持个数 | ||
104 | + }; | ||
105 | + }, | ||
106 | + watch: { | ||
107 | + imgUrl: { | ||
108 | + immediate: true, | ||
109 | + handler(val) { | ||
110 | + console.log("imgUrl:", val); | ||
111 | + if (val) { | ||
112 | + this.fileList = [{ url: val }]; | ||
113 | + } else { | ||
114 | + this.fileList = []; | ||
115 | + } | ||
116 | + }, | ||
117 | + }, | ||
118 | + }, | ||
119 | + computed: {}, | ||
120 | + created() { | ||
121 | + this.headers["Token-Auth"] = getToken(); | ||
122 | + if (this.public) { | ||
123 | + this.data["acl"] = "public-read"; | ||
124 | + } | ||
125 | + }, | ||
126 | + methods: { | ||
127 | + /** | ||
128 | + * 上传图片校验 | ||
129 | + */ | ||
130 | + beforeUpload(file) { | ||
131 | + let isIMG; | ||
132 | + const regList = this.accept.split(","); | ||
133 | + if (this.accept === "image/*") { | ||
134 | + isIMG = /image/i.test(file.type); | ||
135 | + } else { | ||
136 | + isIMG = regList.includes(file.type); // 是否符合类型 | ||
137 | + } | ||
138 | + const isLt6M = file.size / 1024 / 1024 < this.maxSize; | ||
139 | + if (!isIMG) { | ||
140 | + this.fileList = []; | ||
141 | + this.$message.error("图片格式仅支持png、jpg,请更换图片!"); | ||
142 | + } | ||
143 | + if (!isLt6M) { | ||
144 | + this.$message.error(`上传图片大小不能超过${this.maxSize}MB!`); | ||
145 | + } | ||
146 | + console.log("beforeUpload", file, isIMG && isLt6M); | ||
147 | + return isIMG && isLt6M; | ||
148 | + }, | ||
149 | + /** | ||
150 | + * 文件修改 | ||
151 | + */ | ||
152 | + onChange(file, fileList) { | ||
153 | + console.log("onChange file:", file); | ||
154 | + console.log("onChange fileList:", fileList); | ||
155 | + const overMaxSize = file.size / 1024 / 1024 < this.maxSize; | ||
156 | + if (overMaxSize && fileList.length > 0) { | ||
157 | + this.fileList = [fileList[fileList.length - 1]]; | ||
158 | + } else { | ||
159 | + this.fileList = []; | ||
160 | + } | ||
161 | + }, | ||
162 | + /** | ||
163 | + * 上传成功 | ||
164 | + */ | ||
165 | + onSuccess(res, file, fileList) { | ||
166 | + console.log("onSuccess res:", res); | ||
167 | + console.log("onSuccess file:", file); | ||
168 | + console.log("onSuccess fileList:", fileList); | ||
169 | + const { data } = res; | ||
170 | + this.$emit("input", data.file); | ||
171 | + }, | ||
172 | + /** | ||
173 | + * 删除 | ||
174 | + */ | ||
175 | + handleDelete() { | ||
176 | + console.log("handleDelete"); | ||
177 | + this.$emit("input", ""); | ||
178 | + }, | ||
179 | + /** | ||
180 | + * 预览 | ||
181 | + * @param file | ||
182 | + */ | ||
183 | + handlePictureCardPreview(file) { | ||
184 | + this.$refs.zpSingleImg.clickHandler(); | ||
185 | + }, | ||
186 | + handleReload() { | ||
187 | + this.$refs.triggerWrapper.click(); | ||
188 | + }, | ||
189 | + }, | ||
190 | +}; | ||
191 | +</script> | ||
192 | + | ||
193 | +<style lang="less" scoped> | ||
194 | +.zp-single-img-upload__container { | ||
195 | + display: inline-block; | ||
196 | + width: 148px; | ||
197 | + margin-right: 16px; | ||
198 | + /deep/ .el-upload { | ||
199 | + border: 1px dashed #d8dce5; | ||
200 | + } | ||
201 | + /deep/ .el-upload-list__item { | ||
202 | + border: 1px solid #d8dce5; | ||
203 | + } | ||
204 | + .zp-single-img-upload__img-wrapper { | ||
205 | + height: 100%; | ||
206 | + .zp-single-img-upload__img { | ||
207 | + height: 100%; | ||
208 | + width: 100%; | ||
209 | + } | ||
210 | + } | ||
211 | + | ||
212 | + .zp-single-img-upload__reload { | ||
213 | + position: absolute; | ||
214 | + left: 0; | ||
215 | + bottom: 0; | ||
216 | + width: 100%; | ||
217 | + text-align: center; | ||
218 | + background: rgba(51, 51, 51, 0.8); | ||
219 | + color: #fff; | ||
220 | + font-size: 12px; | ||
221 | + cursor: pointer; | ||
222 | + } | ||
223 | + | ||
224 | + /deep/ .el-upload-list { | ||
225 | + position: absolute; | ||
226 | + } | ||
227 | +} | ||
228 | +</style> |
rf-blog/code/admin/src/components/github.vue
0 → 100644
1 | +<template> | ||
2 | + <a | ||
3 | + :href="githubLink" | ||
4 | + target="_blank" | ||
5 | + class="github-corner" | ||
6 | + aria-label="View source on Github" | ||
7 | + > | ||
8 | + <svg | ||
9 | + width="80" | ||
10 | + height="80" | ||
11 | + viewBox="0 0 250 250" | ||
12 | + style="" | ||
13 | + aria-hidden="true" | ||
14 | + > | ||
15 | + <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> | ||
16 | + <path | ||
17 | + d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" | ||
18 | + fill="currentColor" | ||
19 | + style="transform-origin: 130px 106px" | ||
20 | + class="octo-arm" | ||
21 | + ></path> | ||
22 | + <path | ||
23 | + d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" | ||
24 | + fill="currentColor" | ||
25 | + class="octo-body" | ||
26 | + ></path> | ||
27 | + </svg> | ||
28 | + </a> | ||
29 | +</template> | ||
30 | +<script> | ||
31 | +export default { | ||
32 | + name: "Github", | ||
33 | + props: { | ||
34 | + githubLink: { | ||
35 | + type: String, | ||
36 | + default: null, | ||
37 | + }, | ||
38 | + }, | ||
39 | +}; | ||
40 | +</script> | ||
41 | + | ||
42 | +<style lang="less" scoped> | ||
43 | +.github-corner { | ||
44 | + fill: #4ab7bd; | ||
45 | + color: #fff; | ||
46 | + position: absolute; | ||
47 | + top: 50px; | ||
48 | + border: 0; | ||
49 | + right: 0; | ||
50 | + | ||
51 | + &:hover .octo-arm { | ||
52 | + animation: octocat-wave 560ms ease-in-out; | ||
53 | + } | ||
54 | +} | ||
55 | + | ||
56 | +@keyframes octocat-wave { | ||
57 | + 0%, | ||
58 | + 100% { | ||
59 | + transform: rotate(0); | ||
60 | + } | ||
61 | + 20%, | ||
62 | + 60% { | ||
63 | + transform: rotate(-25deg); | ||
64 | + } | ||
65 | + 40%, | ||
66 | + 80% { | ||
67 | + transform: rotate(10deg); | ||
68 | + } | ||
69 | +} | ||
70 | +</style> | ||
71 | + |
1 | +<template> | ||
2 | + <div> | ||
3 | + <svg | ||
4 | + t="1492500959545" | ||
5 | + @click="toggleClick" | ||
6 | + class="wscn-icon hamburger" | ||
7 | + :class="{ 'is-active': isActive }" | ||
8 | + style="" | ||
9 | + viewBox="0 0 1024 1024" | ||
10 | + version="1.1" | ||
11 | + xmlns="http://www.w3.org/2000/svg" | ||
12 | + p-id="1691" | ||
13 | + xmlns:xlink="http://www.w3.org/1999/xlink" | ||
14 | + width="64" | ||
15 | + height="64" | ||
16 | + > | ||
17 | + <path | ||
18 | + d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z" | ||
19 | + p-id="1692" | ||
20 | + ></path> | ||
21 | + <path | ||
22 | + d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z" | ||
23 | + p-id="1693" | ||
24 | + ></path> | ||
25 | + <path | ||
26 | + d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z" | ||
27 | + p-id="1694" | ||
28 | + ></path> | ||
29 | + </svg> | ||
30 | + </div> | ||
31 | +</template> | ||
32 | + | ||
33 | +<script> | ||
34 | +export default { | ||
35 | + name: "hamburger", | ||
36 | + props: { | ||
37 | + isActive: { | ||
38 | + type: Boolean, | ||
39 | + default: false, | ||
40 | + }, | ||
41 | + toggleClick: { | ||
42 | + type: Function, | ||
43 | + default: null, | ||
44 | + }, | ||
45 | + }, | ||
46 | +}; | ||
47 | +</script> | ||
48 | + | ||
49 | +<style scoped> | ||
50 | +.hamburger { | ||
51 | + display: inline-block; | ||
52 | + cursor: pointer; | ||
53 | + width: 20px; | ||
54 | + height: 20px; | ||
55 | + transform: rotate(0deg); | ||
56 | + transition: 0.38s; | ||
57 | + transform-origin: 50% 50%; | ||
58 | +} | ||
59 | + | ||
60 | +.hamburger.is-active { | ||
61 | + transform: rotate(90deg); | ||
62 | +} | ||
63 | +</style> |
rf-blog/code/admin/src/constant/classify.js
0 → 100644
rf-blog/code/admin/src/filters/index.js
0 → 100644
1 | +/** | ||
2 | + * 时间日期格式化 | ||
3 | + * 用法 formatTime(new Date(), 'yyyy-MM-dd hh:mm:ss') | ||
4 | + * @param time | ||
5 | + * @param fmt | ||
6 | + */ | ||
7 | +export function formatTime(time, fmt) { | ||
8 | + time = parseInt(time); | ||
9 | + if (!time) { | ||
10 | + return ""; | ||
11 | + } | ||
12 | + const date = new Date(time); | ||
13 | + let o = { | ||
14 | + "M+": date.getMonth() + 1, // 月份 | ||
15 | + "d+": date.getDate(), // 日 | ||
16 | + "h+": date.getHours(), // 小时 | ||
17 | + "m+": date.getMinutes(), // 分 | ||
18 | + "s+": date.getSeconds(), // 秒 | ||
19 | + "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 | ||
20 | + S: date.getMilliseconds(), // 毫秒 | ||
21 | + }; | ||
22 | + if (/(y+)/.test(fmt)) { | ||
23 | + fmt = fmt.replace( | ||
24 | + RegExp.$1, | ||
25 | + (date.getFullYear() + "").substr(4 - RegExp.$1.length) | ||
26 | + ); | ||
27 | + } | ||
28 | + for (let k in o) { | ||
29 | + if (new RegExp("(" + k + ")").test(fmt)) { | ||
30 | + fmt = fmt.replace( | ||
31 | + RegExp.$1, | ||
32 | + RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length) | ||
33 | + ); | ||
34 | + } | ||
35 | + } | ||
36 | + return fmt; | ||
37 | +} |
rf-blog/code/admin/src/main.js
0 → 100644
1 | +import Vue from "vue"; | ||
2 | +import App from "./App.vue"; | ||
3 | +import router from "./router/permission"; | ||
4 | +import store from "./store"; | ||
5 | +import ElementUI from "element-ui"; | ||
6 | +import "element-ui/lib/theme-chalk/index.css"; | ||
7 | +import "./styles/index.less"; | ||
8 | +import pageInfoMixin from "src/mixins/pageInfo"; | ||
9 | + | ||
10 | +import "./utils/config/iconfont"; | ||
11 | +import "src/components/common/index.js"; | ||
12 | + | ||
13 | +import * as filters from "./filters"; | ||
14 | +Object.keys(filters).forEach((key) => { | ||
15 | + Vue.filter(key, filters[key]); | ||
16 | +}); | ||
17 | + | ||
18 | +Vue.mixin(pageInfoMixin); | ||
19 | +Vue.use(ElementUI); | ||
20 | + | ||
21 | +new Vue({ | ||
22 | + el: "#app", | ||
23 | + router, | ||
24 | + store, | ||
25 | + template: "<App/>", | ||
26 | + components: { App }, | ||
27 | +}); |
rf-blog/code/admin/src/mixins/pageInfo.js
0 → 100644
1 | +/** | ||
2 | + * 1、先引入 pageInfo 的mixin文件,注入mixin | ||
3 | + * 2、给 requestPageData 方法赋值页面翻页查询方法 | ||
4 | + */ | ||
5 | + | ||
6 | +export default { | ||
7 | + data() { | ||
8 | + return { | ||
9 | + requestPageData: null, // 每个table组建的数据源获取方法 | ||
10 | + pageInfo: { | ||
11 | + total: 0, | ||
12 | + pageNum: 1, | ||
13 | + pageSize: 10, | ||
14 | + }, | ||
15 | + }; | ||
16 | + }, | ||
17 | + watch: { | ||
18 | + "pageInfo.pageNum"() { | ||
19 | + this.requestPageData && this.requestPageData(); | ||
20 | + }, | ||
21 | + "pageInfo.pageSize"() { | ||
22 | + this.requestPageData && this.requestPageData(); | ||
23 | + }, | ||
24 | + }, | ||
25 | + methods: { | ||
26 | + handleCurrentChange(page) { | ||
27 | + this.pageInfo.pageNum = page; | ||
28 | + }, | ||
29 | + handleSizeChange(val) { | ||
30 | + this.pageInfo.pageSize = val; | ||
31 | + }, | ||
32 | + }, | ||
33 | +}; |
rf-blog/code/admin/src/router/index.js
0 → 100644
1 | +import Vue from "vue"; | ||
2 | +import Router from "vue-router"; | ||
3 | +Vue.use(Router); | ||
4 | + | ||
5 | +// 解决同一页面,参数不同的路由报错 | ||
6 | +const VueRouterPush = Router.prototype.push; | ||
7 | +Router.prototype.push = function push(to) { | ||
8 | + return VueRouterPush.call(this, to).catch((err) => err); | ||
9 | +}; | ||
10 | + | ||
11 | +/** | ||
12 | + * 加载模块 | ||
13 | + * @param {string | Component} component 路径或组件 | ||
14 | + * @param {boolean} lazy 是否懒加载 | ||
15 | + * @returns {Function | object} 懒加载方法或组件对象 | ||
16 | + */ | ||
17 | +const loadComponent = (component, lazy = true) => | ||
18 | + lazy ? () => import(`views/${component}.vue`) : component; | ||
19 | + | ||
20 | +export const constantRouterMap = [ | ||
21 | + { | ||
22 | + path: "/login", | ||
23 | + name: "登录", | ||
24 | + component: loadComponent("Login/index"), | ||
25 | + hidden: true, | ||
26 | + }, | ||
27 | + { | ||
28 | + path: "/", | ||
29 | + name: "首页", | ||
30 | + component: loadComponent("Layout/index"), | ||
31 | + redirect: "/home", | ||
32 | + icon: "home", | ||
33 | + children: [ | ||
34 | + { path: "home", component: loadComponent("Home/index"), name: "首页" }, | ||
35 | + ], | ||
36 | + }, | ||
37 | +]; | ||
38 | +export const asyncRouterMap = [ | ||
39 | + { | ||
40 | + path: "/permission", | ||
41 | + name: "权限管理", | ||
42 | + meta: { role: ["admin"] }, | ||
43 | + component: loadComponent("Layout/index"), | ||
44 | + redirect: "/permission/list", | ||
45 | + requireAuth: true, // 是否需要登录 | ||
46 | + dropdown: true, | ||
47 | + icon: "authority", | ||
48 | + children: [ | ||
49 | + { | ||
50 | + path: "list", | ||
51 | + component: loadComponent("Permission/list"), | ||
52 | + name: "管理员列表", | ||
53 | + }, | ||
54 | + { | ||
55 | + path: "add", | ||
56 | + component: loadComponent("Permission/add"), | ||
57 | + name: "添加管理员", | ||
58 | + }, | ||
59 | + ], | ||
60 | + }, | ||
61 | + { | ||
62 | + path: "/article", | ||
63 | + name: "文章管理", | ||
64 | + component: loadComponent("Layout/index"), | ||
65 | + redirect: "/article/list", | ||
66 | + dropdown: true, | ||
67 | + icon: "article", | ||
68 | + children: [ | ||
69 | + { | ||
70 | + path: "list", | ||
71 | + component: loadComponent("Article/list"), | ||
72 | + name: "文章列表", | ||
73 | + }, | ||
74 | + { | ||
75 | + path: "edit", | ||
76 | + component: loadComponent("Article/edit"), | ||
77 | + name: "文章编辑", | ||
78 | + hidden: true, | ||
79 | + }, | ||
80 | + { | ||
81 | + path: "add", | ||
82 | + component: loadComponent("Article/add"), | ||
83 | + name: "添加文章", | ||
84 | + }, | ||
85 | + ], | ||
86 | + }, | ||
87 | + { | ||
88 | + path: "/label", | ||
89 | + name: "标签管理", | ||
90 | + component: loadComponent("Layout/index"), | ||
91 | + redirect: "/label/list", | ||
92 | + dropdown: true, | ||
93 | + icon: "label", | ||
94 | + children: [ | ||
95 | + { | ||
96 | + path: "list", | ||
97 | + component: loadComponent("Label/list"), | ||
98 | + name: "标签列表", | ||
99 | + }, | ||
100 | + { | ||
101 | + path: "add", | ||
102 | + component: loadComponent("Label/add"), | ||
103 | + name: "添加标签", | ||
104 | + }, | ||
105 | + ], | ||
106 | + }, | ||
107 | + { | ||
108 | + path: "/message", | ||
109 | + name: "留言管理", | ||
110 | + component: loadComponent("Layout/index"), | ||
111 | + redirect: "/message/list", | ||
112 | + dropdown: true, | ||
113 | + icon: "message", | ||
114 | + children: [ | ||
115 | + { | ||
116 | + path: "list", | ||
117 | + component: loadComponent("Message/list"), | ||
118 | + name: "留言列表", | ||
119 | + }, | ||
120 | + { | ||
121 | + path: "reply", | ||
122 | + component: loadComponent("Message/replyList"), | ||
123 | + name: "回复列表", | ||
124 | + }, | ||
125 | + ], | ||
126 | + }, | ||
127 | +]; | ||
128 | + | ||
129 | +export const router = new Router({ | ||
130 | + // mode: 'history', | ||
131 | + routes: constantRouterMap, | ||
132 | +}); |
rf-blog/code/admin/src/router/permission.js
0 → 100644
1 | +import store from "../store"; | ||
2 | +import { getToken } from "src/utils/auth"; | ||
3 | +import { router } from "./index"; | ||
4 | +import NProgress from "nprogress"; // Progress 进度条 | ||
5 | +import "nprogress/nprogress.css"; // Progress 进度条样式 | ||
6 | + | ||
7 | +router.beforeEach((to, from, next) => { | ||
8 | + NProgress.start(); | ||
9 | + if (getToken()) { | ||
10 | + if (!store.state.user.roles) { | ||
11 | + // 重新拉取用户信息 | ||
12 | + store.dispatch("getUserInfo").then((res) => { | ||
13 | + // 如果token过期,则需重新登录 | ||
14 | + if (res.code === 401) { | ||
15 | + next("/login"); | ||
16 | + } else { | ||
17 | + let roles = res.data.roles; | ||
18 | + store.dispatch("setRoutes", { roles }).then(() => { | ||
19 | + // 根据权限动态添加路由 | ||
20 | + router.addRoutes(store.state.permission.addRouters); | ||
21 | + next({ ...to }); // hash模式 确保路由加载完成 | ||
22 | + }); | ||
23 | + } | ||
24 | + }); | ||
25 | + } else { | ||
26 | + next(); | ||
27 | + } | ||
28 | + } else { | ||
29 | + to.path === "/login" ? next() : next("/login"); | ||
30 | + } | ||
31 | +}); | ||
32 | +router.afterEach((to, from) => { | ||
33 | + document.title = to.name; | ||
34 | + NProgress.done(); | ||
35 | +}); | ||
36 | + | ||
37 | +export default router; |
rf-blog/code/admin/src/store/getters.js
0 → 100644
rf-blog/code/admin/src/store/index.js
0 → 100644
1 | +import Vue from "vue"; | ||
2 | +import Vuex from "vuex"; | ||
3 | +import getters from "./getters"; | ||
4 | +import app from "./modules/app"; | ||
5 | +import user from "./modules/user"; | ||
6 | +import permission from "./modules/permission"; | ||
7 | + | ||
8 | +Vue.use(Vuex); | ||
9 | + | ||
10 | +const store = new Vuex.Store({ | ||
11 | + modules: { | ||
12 | + app, | ||
13 | + user, | ||
14 | + permission, | ||
15 | + }, | ||
16 | + getters, | ||
17 | +}); | ||
18 | + | ||
19 | +export default store; |
rf-blog/code/admin/src/store/modules/app.js
0 → 100644
1 | +import { Local } from "src/utils/storage"; | ||
2 | +const app = { | ||
3 | + state: { | ||
4 | + slideBar: { | ||
5 | + opened: Local.get("slideBarStatus"), | ||
6 | + }, | ||
7 | + tagViews: JSON.parse(Local.get("tagViews")) || [], | ||
8 | + is_add_router: false, | ||
9 | + }, | ||
10 | + mutations: { | ||
11 | + TOGGLE_SIDEBAR(state) { | ||
12 | + if (state.slideBar.opened) { | ||
13 | + Local.set("slideBarStatus", false); | ||
14 | + } else { | ||
15 | + Local.set("slideBarStatus", true); | ||
16 | + } | ||
17 | + state.slideBar.opened = !state.slideBar.opened; | ||
18 | + }, | ||
19 | + ADD_TAGVIEW(state, tag) { | ||
20 | + if (state.tagViews.some((v) => v.name === tag.name)) return; | ||
21 | + state.tagViews.push({ name: tag.name, path: tag.path }); | ||
22 | + Local.set("tagViews", JSON.stringify(state.tagViews)); | ||
23 | + }, | ||
24 | + DEL_TAGVIEW(state, tag) { | ||
25 | + let index; | ||
26 | + for (let [i, v] of state.tagViews.entries()) { | ||
27 | + if (v.name === tag.name) index = i; | ||
28 | + } | ||
29 | + state.tagViews.splice(index, 1); | ||
30 | + Local.set("tagViews", JSON.stringify(state.tagViews)); | ||
31 | + }, | ||
32 | + }, | ||
33 | + actions: { | ||
34 | + toggleSideBar({ commit }) { | ||
35 | + commit("TOGGLE_SIDEBAR"); | ||
36 | + }, | ||
37 | + addTagView({ commit }, tag) { | ||
38 | + commit("ADD_TAGVIEW", tag); | ||
39 | + }, | ||
40 | + delTagView({ commit }, tag) { | ||
41 | + commit("DEL_TAGVIEW", tag); | ||
42 | + }, | ||
43 | + }, | ||
44 | +}; | ||
45 | +export default app; |
1 | +import { constantRouterMap, asyncRouterMap } from "src/router"; | ||
2 | + | ||
3 | +/** | ||
4 | + * 通过meta.role判断是否与当前用户权限匹配 | ||
5 | + * @param role | ||
6 | + * @param route | ||
7 | + */ | ||
8 | +const hasPermission = (roles, route) => { | ||
9 | + if (route.meta && route.meta.role) { | ||
10 | + return roles.some((role) => route.meta.role.indexOf(role) >= 0); | ||
11 | + } else { | ||
12 | + return true; | ||
13 | + } | ||
14 | +}; | ||
15 | + | ||
16 | +/** | ||
17 | + * 递归过滤异步路由表,返回符合用户角色权限的路由表 | ||
18 | + * @param asyncRouterMap | ||
19 | + * @param role | ||
20 | + */ | ||
21 | +const filterAsyncRouter = (asyncRouterMap, roles) => { | ||
22 | + const accessedRouters = asyncRouterMap.filter((route) => { | ||
23 | + if (hasPermission(roles, route)) { | ||
24 | + if (route.children && route.children.length) { | ||
25 | + route.children = filterAsyncRouter(route.children, roles); | ||
26 | + } | ||
27 | + return true; | ||
28 | + } | ||
29 | + return false; | ||
30 | + }); | ||
31 | + return accessedRouters; | ||
32 | +}; | ||
33 | + | ||
34 | +const permission = { | ||
35 | + state: { | ||
36 | + routes: constantRouterMap.concat(asyncRouterMap), | ||
37 | + addRouters: [], | ||
38 | + }, | ||
39 | + mutations: { | ||
40 | + SETROUTES(state, routers) { | ||
41 | + state.addRouters = routers; | ||
42 | + state.routes = constantRouterMap.concat(routers); | ||
43 | + }, | ||
44 | + }, | ||
45 | + actions: { | ||
46 | + setRoutes({ commit }, info) { | ||
47 | + return new Promise((resolve, reject) => { | ||
48 | + let { roles } = info; | ||
49 | + let accessedRouters = []; | ||
50 | + if (roles.indexOf("admin") >= 0) { | ||
51 | + accessedRouters = asyncRouterMap; | ||
52 | + } else { | ||
53 | + accessedRouters = filterAsyncRouter(asyncRouterMap, roles); | ||
54 | + } | ||
55 | + | ||
56 | + commit("SETROUTES", accessedRouters); | ||
57 | + resolve(); | ||
58 | + }); | ||
59 | + }, | ||
60 | + }, | ||
61 | +}; | ||
62 | +export default permission; |
rf-blog/code/admin/src/store/modules/user.js
0 → 100644
1 | +import axios from "src/utils/request"; | ||
2 | +import { getToken } from "src/utils/auth"; | ||
3 | +import md5 from "js-md5"; | ||
4 | + | ||
5 | +const user = { | ||
6 | + state: { | ||
7 | + list: [], | ||
8 | + total: 0, | ||
9 | + username: "", | ||
10 | + roles: null, | ||
11 | + token: getToken(), | ||
12 | + otherList: [], | ||
13 | + }, | ||
14 | + mutations: { | ||
15 | + SET_TOKEN(state, token) { | ||
16 | + state.token = token; | ||
17 | + }, | ||
18 | + SET_USERINFO(state, info) { | ||
19 | + state.username = info.username; | ||
20 | + state.roles = info.roles; | ||
21 | + }, | ||
22 | + USERLIST(state, data) { | ||
23 | + state.list = data.list; | ||
24 | + state.total = data.list.length || 0; | ||
25 | + }, | ||
26 | + GET_INFOLIST(state, data) { | ||
27 | + state.otherList = data; | ||
28 | + }, | ||
29 | + CLEARINFO(state) { | ||
30 | + state.username = ""; | ||
31 | + state.roles = null; | ||
32 | + }, | ||
33 | + }, | ||
34 | + actions: { | ||
35 | + clearInfo({ commit }) { | ||
36 | + commit("CLEARINFO"); | ||
37 | + }, | ||
38 | + userLogin({ state, commit }, info) { | ||
39 | + let { username, pwd } = info; | ||
40 | + return new Promise((resolve, reject) => { | ||
41 | + axios | ||
42 | + .post("/user/login", { | ||
43 | + username: username, | ||
44 | + pwd: md5(pwd), | ||
45 | + }) | ||
46 | + .then((res) => { | ||
47 | + state.token = getToken(); | ||
48 | + resolve(res); | ||
49 | + }) | ||
50 | + .catch((err) => { | ||
51 | + reject(err); | ||
52 | + }); | ||
53 | + }); | ||
54 | + }, | ||
55 | + getUserInfo({ state, commit }) { | ||
56 | + return new Promise((resolve, reject) => { | ||
57 | + axios | ||
58 | + .get("/user/info", { | ||
59 | + token: state.token, | ||
60 | + }) | ||
61 | + .then((res) => { | ||
62 | + commit("SET_USERINFO", res.data); | ||
63 | + resolve(res); | ||
64 | + }) | ||
65 | + .catch((err) => { | ||
66 | + // console.log(err) | ||
67 | + reject(err); | ||
68 | + }); | ||
69 | + }); | ||
70 | + }, | ||
71 | + getUserList({ commit }, params) { | ||
72 | + return new Promise((resolve, reject) => { | ||
73 | + axios | ||
74 | + .get("/user/list", params) | ||
75 | + .then((res) => { | ||
76 | + commit("USERLIST", res.data); | ||
77 | + resolve(res); | ||
78 | + }) | ||
79 | + .catch((err) => { | ||
80 | + // console.log(err) | ||
81 | + reject(err); | ||
82 | + }); | ||
83 | + }); | ||
84 | + }, | ||
85 | + addUser({ commit }, info) { | ||
86 | + info.pwd = md5(info.pwd); | ||
87 | + return new Promise((resolve, reject) => { | ||
88 | + axios | ||
89 | + .post("/user/add", info) | ||
90 | + .then((res) => { | ||
91 | + resolve(res); | ||
92 | + }) | ||
93 | + .catch((err) => { | ||
94 | + reject(err); | ||
95 | + }); | ||
96 | + }); | ||
97 | + }, | ||
98 | + delUser({ commit }, id) { | ||
99 | + return new Promise((resolve, reject) => { | ||
100 | + axios | ||
101 | + .get("/user/del", { id: id }) | ||
102 | + .then((res) => { | ||
103 | + resolve(res); | ||
104 | + }) | ||
105 | + .catch((err) => { | ||
106 | + reject(err); | ||
107 | + }); | ||
108 | + }); | ||
109 | + }, | ||
110 | + updateUser({ commit }, info) { | ||
111 | + info.pwd = md5(info.pwd); | ||
112 | + info.old_pwd = md5(info.old_pwd); | ||
113 | + return new Promise((resolve, reject) => { | ||
114 | + axios | ||
115 | + .post("/user/update", info) | ||
116 | + .then((res) => { | ||
117 | + resolve(res); | ||
118 | + }) | ||
119 | + .catch((err) => { | ||
120 | + reject(err); | ||
121 | + }); | ||
122 | + }); | ||
123 | + }, | ||
124 | + }, | ||
125 | +}; | ||
126 | + | ||
127 | +export default user; |
rf-blog/code/admin/src/styles/index.less
0 → 100644
1 | +@bg-color: #324157; | ||
2 | +@font-color: #bfcbd9; | ||
3 | +@font-hover-color: #48576a; | ||
4 | +@active-font-color: #409EFF; | ||
5 | +@submenu-color: #1f2d3d; | ||
6 | +.el-menu-vertical { | ||
7 | + background-color: @bg-color; | ||
8 | + .el-submenu__title { | ||
9 | + color: @font-color; | ||
10 | + background-color: @bg-color; | ||
11 | + } | ||
12 | + .el-submenu .el-menu { | ||
13 | + background-color: @submenu-color; | ||
14 | + .el-menu-item { | ||
15 | + background: transparent; | ||
16 | + } | ||
17 | + } | ||
18 | + .el-menu-item { | ||
19 | + color: @font-color; | ||
20 | + background: @bg-color; | ||
21 | + } | ||
22 | + .el-menu-item:hover, | ||
23 | + .el-submenu__title:hover { | ||
24 | + background-color: @font-hover-color; | ||
25 | + } | ||
26 | + .el-menu-item.is-active { | ||
27 | + color: @active-font-color; | ||
28 | + background-color: transparent; | ||
29 | + } | ||
30 | +} | ||
31 | + | ||
32 | +.music-cover-uploader { | ||
33 | + .el-upload { | ||
34 | + border: 1px dashed #d9d9d9; | ||
35 | + border-radius: 6px; | ||
36 | + cursor: pointer; | ||
37 | + position: relative; | ||
38 | + overflow: hidden; | ||
39 | + } | ||
40 | + .el-upload:hover { | ||
41 | + border-color: #409EFF; | ||
42 | + } | ||
43 | + .avatar-uploader-icon { | ||
44 | + font-size: 28px; | ||
45 | + color: #8c939d; | ||
46 | + width: 178px; | ||
47 | + height: 178px; | ||
48 | + line-height: 178px; | ||
49 | + text-align: center; | ||
50 | + } | ||
51 | + .avatar { | ||
52 | + width: 178px; | ||
53 | + height: 178px; | ||
54 | + display: block; | ||
55 | + } | ||
56 | +} | ||
57 | +.el-breadcrumb__inner, .el-breadcrumb__inner a { | ||
58 | + color: #48576a; | ||
59 | + font-weight: 400; | ||
60 | +} | ||
61 | + | ||
62 | +.el-breadcrumb__item:last-child .el-breadcrumb__inner, .el-breadcrumb__item:last-child .el-breadcrumb__inner a, .el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover, .el-breadcrumb__item:last-child .el-breadcrumb__inner:hover { | ||
63 | + color: #97a8be; | ||
64 | + cursor: text; | ||
65 | + font-weight: 400; | ||
66 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/admin/src/styles/less/init.less
0 → 100644
1 | +.icon { | ||
2 | + width: 1em; | ||
3 | + height: 1em; | ||
4 | + vertical-align: -0.15em; | ||
5 | + fill: currentColor; | ||
6 | + overflow: hidden; | ||
7 | +} | ||
8 | + | ||
9 | +::-webkit-scrollbar { | ||
10 | + width: 4px; | ||
11 | + height: 4px; | ||
12 | +} | ||
13 | + | ||
14 | +::-webkit-scrollbar-thumb { | ||
15 | + opacity: 0.8; | ||
16 | + background: #ddd; | ||
17 | + border-radius: 4px; | ||
18 | + transition: all 0.5s; | ||
19 | +} |
1 | +/* normalize.css */ | ||
2 | +html { | ||
3 | + line-height: 1.15; /* 1 */ | ||
4 | + -ms-text-size-adjust: 100%; /* 2 */ | ||
5 | + -webkit-text-size-adjust: 100%; /* 2 */ | ||
6 | +} | ||
7 | + | ||
8 | +body { | ||
9 | + margin: 0; | ||
10 | +} | ||
11 | + | ||
12 | +article, | ||
13 | +aside, | ||
14 | +footer, | ||
15 | +header, | ||
16 | +nav, | ||
17 | +section { | ||
18 | + display: block; | ||
19 | +} | ||
20 | + | ||
21 | +h1 { | ||
22 | + font-size: 2em; | ||
23 | + margin: 0.67em 0; | ||
24 | +} | ||
25 | + | ||
26 | +figcaption, | ||
27 | +figure, | ||
28 | +main { | ||
29 | + /* 1 */ | ||
30 | + display: block; | ||
31 | +} | ||
32 | + | ||
33 | +figure { | ||
34 | + margin: 1em 40px; | ||
35 | +} | ||
36 | + | ||
37 | +hr { | ||
38 | + box-sizing: content-box; /* 1 */ | ||
39 | + height: 0; /* 1 */ | ||
40 | + overflow: visible; /* 2 */ | ||
41 | +} | ||
42 | + | ||
43 | +pre { | ||
44 | + font-family: monospace, monospace; /* 1 */ | ||
45 | + font-size: 1em; /* 2 */ | ||
46 | +} | ||
47 | + | ||
48 | +a { | ||
49 | + background-color: transparent; /* 1 */ | ||
50 | + -webkit-text-decoration-skip: objects; /* 2 */ | ||
51 | +} | ||
52 | + | ||
53 | +abbr[title] { | ||
54 | + border-bottom: none; /* 1 */ | ||
55 | + text-decoration: underline; /* 2 */ | ||
56 | + text-decoration: underline dotted; /* 2 */ | ||
57 | +} | ||
58 | + | ||
59 | +b, | ||
60 | +strong { | ||
61 | + font-weight: inherit; | ||
62 | +} | ||
63 | + | ||
64 | +b, | ||
65 | +strong { | ||
66 | + font-weight: bolder; | ||
67 | +} | ||
68 | + | ||
69 | +code, | ||
70 | +kbd, | ||
71 | +samp { | ||
72 | + font-family: monospace, monospace; /* 1 */ | ||
73 | + font-size: 1em; /* 2 */ | ||
74 | +} | ||
75 | + | ||
76 | +dfn { | ||
77 | + font-style: italic; | ||
78 | +} | ||
79 | + | ||
80 | +mark { | ||
81 | + background-color: #ff0; | ||
82 | + color: @mainColor; | ||
83 | +} | ||
84 | + | ||
85 | +small { | ||
86 | + font-size: 80%; | ||
87 | +} | ||
88 | + | ||
89 | +sub, | ||
90 | +sup { | ||
91 | + font-size: 75%; | ||
92 | + line-height: 0; | ||
93 | + position: relative; | ||
94 | + vertical-align: baseline; | ||
95 | +} | ||
96 | + | ||
97 | +sub { | ||
98 | + bottom: -0.25em; | ||
99 | +} | ||
100 | + | ||
101 | +sup { | ||
102 | + top: -0.5em; | ||
103 | +} | ||
104 | + | ||
105 | +audio, | ||
106 | +video { | ||
107 | + display: inline-block; | ||
108 | +} | ||
109 | +audio:not([controls]) { | ||
110 | + display: none; | ||
111 | + height: 0; | ||
112 | +} | ||
113 | + | ||
114 | +img { | ||
115 | + border-style: none; | ||
116 | +} | ||
117 | + | ||
118 | +svg:not(:root) { | ||
119 | + overflow: hidden; | ||
120 | +} | ||
121 | + | ||
122 | +button, | ||
123 | +input, | ||
124 | +optgroup, | ||
125 | +select, | ||
126 | +textarea { | ||
127 | + font-family: sans-serif; /* 1 */ | ||
128 | + font-size: 100%; /* 1 */ | ||
129 | + line-height: 1.15; /* 1 */ | ||
130 | + margin: 0; /* 2 */ | ||
131 | +} | ||
132 | +button, | ||
133 | +input { | ||
134 | + /* 1 */ | ||
135 | + overflow: visible; | ||
136 | +} | ||
137 | +button, | ||
138 | +select { | ||
139 | + /* 1 */ | ||
140 | + text-transform: none; | ||
141 | +} | ||
142 | + | ||
143 | +button, | ||
144 | + html [type="button"], /* 1 */ | ||
145 | + [type="reset"], | ||
146 | + [type="submit"] { | ||
147 | + -webkit-appearance: button; /* 2 */ | ||
148 | +} | ||
149 | + | ||
150 | +button::-moz-focus-inner, | ||
151 | +[type="button"]::-moz-focus-inner, | ||
152 | +[type="reset"]::-moz-focus-inner, | ||
153 | +[type="submit"]::-moz-focus-inner { | ||
154 | + border-style: none; | ||
155 | + padding: 0; | ||
156 | +} | ||
157 | + | ||
158 | +button:-moz-focusring, | ||
159 | +[type="button"]:-moz-focusring, | ||
160 | +[type="reset"]:-moz-focusring, | ||
161 | +[type="submit"]:-moz-focusring { | ||
162 | + outline: 1px dotted ButtonText; | ||
163 | +} | ||
164 | + | ||
165 | +fieldset { | ||
166 | + padding: 0.35em 0.75em 0.625em; | ||
167 | +} | ||
168 | + | ||
169 | +legend { | ||
170 | + box-sizing: border-box; /* 1 */ | ||
171 | + color: inherit; /* 2 */ | ||
172 | + display: table; /* 1 */ | ||
173 | + max-width: 100%; /* 1 */ | ||
174 | + padding: 0; /* 3 */ | ||
175 | + white-space: normal; /* 1 */ | ||
176 | +} | ||
177 | + | ||
178 | +progress { | ||
179 | + display: inline-block; /* 1 */ | ||
180 | + vertical-align: baseline; /* 2 */ | ||
181 | +} | ||
182 | + | ||
183 | +textarea { | ||
184 | + overflow: auto; | ||
185 | +} | ||
186 | + | ||
187 | +[type="checkbox"], | ||
188 | +[type="radio"] { | ||
189 | + box-sizing: border-box; /* 1 */ | ||
190 | + padding: 0; /* 2 */ | ||
191 | +} | ||
192 | + | ||
193 | +[type="number"]::-webkit-inner-spin-button, | ||
194 | +[type="number"]::-webkit-outer-spin-button { | ||
195 | + height: auto; | ||
196 | +} | ||
197 | + | ||
198 | +[type="search"] { | ||
199 | + -webkit-appearance: textfield; /* 1 */ | ||
200 | + outline-offset: -2px; /* 2 */ | ||
201 | +} | ||
202 | + | ||
203 | +[type="search"]::-webkit-search-cancel-button, | ||
204 | +[type="search"]::-webkit-search-decoration { | ||
205 | + -webkit-appearance: none; | ||
206 | +} | ||
207 | + | ||
208 | +::-webkit-file-upload-button { | ||
209 | + -webkit-appearance: button; /* 1 */ | ||
210 | + font: inherit; /* 2 */ | ||
211 | +} | ||
212 | + | ||
213 | +details, /* 1 */ | ||
214 | + menu { | ||
215 | + display: block; | ||
216 | +} | ||
217 | + | ||
218 | +summary { | ||
219 | + display: list-item; | ||
220 | +} | ||
221 | + | ||
222 | +canvas { | ||
223 | + display: inline-block; | ||
224 | +} | ||
225 | + | ||
226 | +template { | ||
227 | + display: none; | ||
228 | +} | ||
229 | + | ||
230 | +[hidden] { | ||
231 | + display: none; | ||
232 | +} | ||
233 | + | ||
234 | +/* reset */ | ||
235 | +* { | ||
236 | + box-sizing: border-box; | ||
237 | +} | ||
238 | +html, | ||
239 | +body { | ||
240 | + font: Oswald, "Open Sans", Helvetica, Arial, sans-serif; | ||
241 | +} | ||
242 | +/* 禁止长按链接与图片弹出菜单 */ | ||
243 | +a, | ||
244 | +img { | ||
245 | + -webkit-touch-callout: none; | ||
246 | +} | ||
247 | + | ||
248 | +/*ios android去除自带阴影的样式*/ | ||
249 | +a, | ||
250 | +input { | ||
251 | + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | ||
252 | +} | ||
253 | + | ||
254 | +input[type="text"] { | ||
255 | + -webkit-appearance: none; | ||
256 | +} | ||
257 | + | ||
258 | +html, | ||
259 | +body, | ||
260 | +h1, | ||
261 | +h2, | ||
262 | +h3, | ||
263 | +h4, | ||
264 | +h5, | ||
265 | +h6, | ||
266 | +div, | ||
267 | +dl, | ||
268 | +dt, | ||
269 | +dd, | ||
270 | +ul, | ||
271 | +ol, | ||
272 | +li, | ||
273 | +p, | ||
274 | +blockquote, | ||
275 | +pre, | ||
276 | +hr, | ||
277 | +figure, | ||
278 | +table, | ||
279 | +caption, | ||
280 | +th, | ||
281 | +td, | ||
282 | +form, | ||
283 | +fieldset, | ||
284 | +legend, | ||
285 | +input, | ||
286 | +button, | ||
287 | +textarea, | ||
288 | +menu { | ||
289 | + margin: 0; | ||
290 | + padding: 0; | ||
291 | +} | ||
292 | +header, | ||
293 | +footer, | ||
294 | +section, | ||
295 | +article, | ||
296 | +aside, | ||
297 | +nav, | ||
298 | +hgroup, | ||
299 | +address, | ||
300 | +figure, | ||
301 | +figcaption, | ||
302 | +menu, | ||
303 | +details { | ||
304 | + display: block; | ||
305 | +} | ||
306 | +table { | ||
307 | + border-collapse: collapse; | ||
308 | + border-spacing: 0; | ||
309 | +} | ||
310 | +caption, | ||
311 | +th { | ||
312 | + text-align: left; | ||
313 | + font-weight: normal; | ||
314 | +} | ||
315 | +html, | ||
316 | +body, | ||
317 | +fieldset, | ||
318 | +img, | ||
319 | +iframe, | ||
320 | +abbr { | ||
321 | + border: 0; | ||
322 | +} | ||
323 | +i, | ||
324 | +cite, | ||
325 | +em, | ||
326 | +var, | ||
327 | +address, | ||
328 | +dfn { | ||
329 | + font-style: normal; | ||
330 | +} | ||
331 | +[hidefocus], | ||
332 | +summary { | ||
333 | + outline: 0; | ||
334 | +} | ||
335 | +li { | ||
336 | + list-style: none; | ||
337 | +} | ||
338 | +h1, | ||
339 | +h2, | ||
340 | +h3, | ||
341 | +h4, | ||
342 | +h5, | ||
343 | +h6, | ||
344 | +small { | ||
345 | + font-size: 100%; | ||
346 | +} | ||
347 | +sup, | ||
348 | +sub { | ||
349 | + font-size: 83%; | ||
350 | +} | ||
351 | +pre, | ||
352 | +code, | ||
353 | +kbd, | ||
354 | +samp { | ||
355 | + font-family: inherit; | ||
356 | +} | ||
357 | +q:before, | ||
358 | +q:after { | ||
359 | + content: none; | ||
360 | +} | ||
361 | +textarea { | ||
362 | + overflow: auto; | ||
363 | + resize: none; | ||
364 | +} | ||
365 | +label, | ||
366 | +summary { | ||
367 | + cursor: default; | ||
368 | +} | ||
369 | +a, | ||
370 | +button { | ||
371 | + cursor: pointer; | ||
372 | +} | ||
373 | +h1, | ||
374 | +h2, | ||
375 | +h3, | ||
376 | +h4, | ||
377 | +h5, | ||
378 | +h6, | ||
379 | +em, | ||
380 | +strong, | ||
381 | +b { | ||
382 | + font-weight: bold; | ||
383 | +} | ||
384 | +del, | ||
385 | +ins, | ||
386 | +u, | ||
387 | +s, | ||
388 | +a, | ||
389 | +a:hover { | ||
390 | + text-decoration: none; | ||
391 | +} |
rf-blog/code/admin/src/utils/auth.js
0 → 100644
1 | +import { Cookie } from "./storage"; | ||
2 | +const TokenKey = "Token-Auth"; | ||
3 | + | ||
4 | +export function getToken() { | ||
5 | + return Cookie.get(TokenKey); | ||
6 | +} | ||
7 | + | ||
8 | +export function setToken(token) { | ||
9 | + return Cookie.set(TokenKey, token); | ||
10 | +} | ||
11 | + | ||
12 | +export function removeToken() { | ||
13 | + return Cookie.remove(TokenKey); | ||
14 | +} |
1 | +!(function(c) { | ||
2 | + var l, | ||
3 | + t, | ||
4 | + a, | ||
5 | + e, | ||
6 | + o, | ||
7 | + i = | ||
8 | + '<svg><symbol id="icon-password" viewBox="0 0 1024 1024"><path d="M781.994667 453.632 781.994667 293.546667c0-146.773333-119.125333-265.898667-265.898667-265.898667-1.706667 0-3.072 0-5.12 0-1.365333 0-3.413333 0-4.778667 0-146.773333 0-265.898667 119.125333-265.898667 265.898667l0 160.085333L146.090667 453.632 146.090667 1003.52l732.16 0L878.250667 453.632 781.994667 453.632 781.994667 453.632zM595.626667 902.826667l-165.888 0 40.618667-177.834667c-24.234667-14.336-40.618667-40.96-40.618667-71.338667 0-45.738667 37.205333-82.944 82.944-82.944 45.738667 0 82.944 37.205333 82.944 82.944 0 30.378667-16.384 57.002667-40.96 71.68L595.626667 902.826667 595.626667 902.826667zM648.874667 453.632l-275.456 0L373.418667 293.546667c0-73.386667 59.733333-132.778667 132.778667-132.778667 2.048 0 6.485333 0 6.485333 0s2.389333 0 3.413333 0c73.386667 0 132.778667 59.733333 132.778667 132.778667L648.874667 453.632 648.874667 453.632zM648.874667 453.632" ></path></symbol><symbol id="icon-label" viewBox="0 0 1024 1024"><path d="M115.7 615.7c-19.4-19.4-19.4-50.8 0-70.1L567.9 93.3l0.2-0.2c3.2-3.1 1.1-8.5-3.4-8.5h-53.3c-7.2 0.5-13.9 3.6-19 8.6L43.6 542.1c-21.3 21.3-21.3 55.8 0 77.1l320.7 320.7c11.6 11.6 30.5 11.6 42.1 0 9.2-9.2 9.2-24.2 0-33.5L115.7 615.7z" ></path><path d="M944.8 97.8l-313.5 19.1c-6.7 0.4-13.1 3.3-17.8 8L193.9 544.4c-18 18-18 47.1 0 65l300.8 300.8c10.8 10.8 28.3 10.8 39 0l432.6-432.6c4.8-4.8 7.6-11.1 8-17.8l19.2-313.3c1.7-27.6-21.2-50.4-48.7-48.7z m-124.7 232c-32.5 0-58.8-26.3-58.8-58.8s26.3-58.8 58.8-58.8 58.8 26.3 58.8 58.8-26.4 58.8-58.8 58.8z" ></path></symbol><symbol id="icon-authority" viewBox="0 0 1024 1024"><path d="M898.34 188.81L538.35 74.51l-359.87 114.3c-13.53 4.27-22.67 16.86-22.55 31.1v454.94c0.72 3.09 17.69 77.98 91.27 150.5 56.61 55.78 192.4 127.48 251.63 158.57 9.25 4.87 13.65 6.65 17.56 8.78l17.57 8.78c4.63 2.49 12.94 2.38 17.57 0l17.57-8.78c34.65-16.97 195-102.9 260.4-167.35 73.71-72.52 90.68-147.41 91.39-150.5V219.91c0.23-14.24-9.02-26.83-22.55-31.1zM542.86 930.98l-0.24-0.12h0.59c-0.23 0.12-0.35 0.12-0.35 0.12z m85.81-264.32h-58.16v81.9c0 18.99-15.43 34.42-34.42 34.42-18.99 0-34.42-15.43-34.42-34.42V537.29c0-0.71 0.12-1.3 0.12-1.9-6.29-1.9-12.58-4.16-18.87-7-21.24-9.73-39.4-27.18-51.51-47-12.58-20.65-19.23-46.53-17.21-70.74 2.37-27.65 12.34-51.75 30.03-73.23 30.62-37.15 86.88-52.22 131.98-35.73 25.52 9.38 46.29 25.05 61.96 47.12 13.77 19.35 20.77 43.44 21.37 67.06 0 0.71 0.12 1.3 0 2.02 0 0.83 0 1.66-0.12 2.49-1.66 52.46-37.74 101.95-89.02 115.61v60.41h58.16c18.99 0 34.42 15.43 34.42 34.42v1.42h0.12c-0.01 18.99-15.44 34.42-34.43 34.42z" ></path><path d="M577.99 449.1c0.35-0.59 0.71-1.07 0.83-1.19a97.38 97.38 0 0 0 5.58-9.74c1.54-4.39 2.73-8.78 3.68-13.41 0.12-2.25 0.24-4.63 0.36-6.89 0-2.26-0.12-4.63-0.36-6.88-0.83-4.51-2.14-9.02-3.68-13.41a108.73 108.73 0 0 0-4.51-8.19c-0.36-0.36-1.19-1.54-2.61-3.8-1.3-1.43-2.61-2.97-4.03-4.27-1.66-1.66-3.44-3.32-5.34-4.87-0.59-0.35-1.06-0.71-1.19-0.83a97.226 97.226 0 0 0-9.73-5.58c-4.39-1.54-8.78-2.73-13.29-3.68-4.63-0.35-9.26-0.35-13.89 0-4.51 0.83-9.02 2.14-13.29 3.68a108.73 108.73 0 0 0-8.19 4.51c-0.36 0.36-1.54 1.19-3.8 2.61-1.43 1.3-2.97 2.61-4.27 4.03-1.66 1.66-3.32 3.44-4.87 5.34-0.35 0.59-0.71 1.07-0.83 1.19a97.226 97.226 0 0 0-5.58 9.73c-1.54 4.39-2.73 8.78-3.68 13.29-0.35 4.63-0.35 9.26 0 13.89 0.83 4.51 2.14 9.02 3.68 13.29 1.42 2.85 2.85 5.46 4.51 8.19 0.36 0.36 1.19 1.54 2.61 3.8 1.3 1.43 2.61 2.97 4.03 4.27 1.66 1.66 3.44 3.32 5.34 4.87 0.59 0.35 1.07 0.71 1.19 0.83 3.08 2.02 6.41 3.92 9.73 5.58 4.39 1.54 8.78 2.73 13.29 3.68 4.63 0.36 9.26 0.36 13.89 0 4.51-0.83 9.02-2.14 13.29-3.68 2.85-1.42 5.46-2.85 8.19-4.51 0.36-0.36 1.54-1.19 3.8-2.61 1.54-1.19 2.97-2.49 4.27-3.92 1.67-1.64 3.33-3.42 4.87-5.32z" ></path></symbol><symbol id="icon-home" viewBox="0 0 1024 1024"><path d="M1024 590.432l-512-397.44-512 397.44 0-162.048 512-397.44 512 397.44zM896 576l0 384-256 0 0-256-256 0 0 256-256 0 0-384 384-288z" ></path></symbol><symbol id="icon-article" viewBox="0 0 1024 1024"><path d="M803.885176 571.286588c-67.026824 0-121.554824 54.528-121.554823 121.554824s54.528 121.554824 121.554823 121.554823c67.026824 0 121.554824-54.528 121.554824-121.554823s-54.512941-121.554824-121.554824-121.554824z m-1.731764 166.821647a45.989647 45.989647 0 1 1-0.015059-91.994353 45.989647 45.989647 0 0 1 0.015059 91.994353z" ></path><path d="M1019.286588 669.530353c0-7.228235-3.222588-13.778824-10.149647-15.811765l-28.672-7.198117c-4.502588-18.025412-10.420706-34.996706-19.802353-50.477177l12.965647-22.588235c3.478588-6.339765 2.439529-15.706353-2.68047-20.826353L941.778824 523.294118c-3.192471-3.192471-8.011294-4.909176-12.724706-4.909177-2.831059 0-5.632 0.617412-8.026353 1.92753l-22.994824 12.604235a182.407529 182.407529 0 0 0-49.724235-20.299294l-7.469177-20.61553c-2.032941-6.942118-9.396706-13.568-16.624941-13.568l-41.607529 0.692706c-7.228235 0-14.878118 5.948235-16.911059 12.890353l-7.664941 23.702588c-17.227294 4.472471-33.460706 10.164706-48.338824 19.124706l-24.259764-13.884235a17.694118 17.694118 0 0 0-8.417883-2.032941c-4.623059 0-9.291294 1.596235-12.40847 4.713412l-29.334589 29.168941c-5.104941 5.104941-6.460235 14.411294-2.981647 20.751059l13.507765 24.681411a182.482824 182.482824 0 0 0-19.636706 47.585883l-25.479529 7.936c-6.927059 2.032941-16.308706 9.592471-16.308706 16.820706l0.406588 41.472c0 7.228235 8.96 14.802824 15.902118 16.835764l23.009882 7.951059c4.306824 16.941176 12.227765 32.918588 20.87153 47.600941l-12.905412 24.71153c-3.463529 6.339765-1.822118 15.706353 3.297882 20.826353l29.485177 29.334588c3.192471 3.192471 8.071529 4.909176 12.830117 4.909176 2.861176 0 5.677176-0.617412 8.07153-1.927529l24.304941-13.266824a182.678588 182.678588 0 0 0 48.353882 20.344471l7.68 24.515765c2.032941 6.942118 9.035294 16.956235 16.26353 16.956235l42.405647 0.030118c7.228235 0 14.456471-10.059294 16.489411-16.986353l7.484236-21.443765c17.724235-4.336941 34.439529-12.468706 49.739294-21.534118l23.04 11.986824c2.288941 1.249882 4.954353 1.837176 7.68 1.837176 4.848941 0 9.878588-1.852235 13.146353-5.135058l29.334588-29.485177c5.104941-5.104941 6.460235-14.561882 2.981647-20.901647l-12.348235-22.633412a182.934588 182.934588 0 0 0 21.037176-50.492235l26.217412-7.213177c6.927059-2.032941 10.767059-9.622588 10.767059-16.850823l-0.632471-42.496zM803.885176 820.976941c-70.640941 0-128.135529-57.479529-128.135529-128.135529s57.479529-128.135529 128.135529-128.13553 128.135529 57.479529 128.13553 128.13553-57.479529 128.135529-128.13553 128.135529zM461.507765 94.328471a75.158588 75.158588 0 0 0-57.569883 7.243294l-0.150588 0.075294h75.14353c-5.436235 0-11.218824-5.616941-17.423059-7.318588z" ></path><path d="M592.941176 752.775529c-16.248471-5.074824-33.264941-20.886588-33.264941-40.493176l-0.421647-43.866353c0-2.544941 0.301176-9.592471 0.828236-9.592471H398.245647a7.544471 7.544471 0 1 1 0-15.058823h169.155765c6.656 0 16.429176-13.372235 25.825882-16.112941l12.619294-2.891294c3.117176-9.411765 6.881882-18.085647 11.324236-27.000471l-6.987295-12.423529a43.188706 43.188706 0 0 1 7.243295-50.492236l29.123764-29.078588a42.059294 42.059294 0 0 1 29.861647-12.032c7.288471 0 13.748706 1.776941 20.284236 5.360941l10.947764 6.957177c8.493176-4.080941 15.284706-7.695059 25.675294-10.917647l-4.336941-10.752c5.315765-17.438118 12.679529-30.674824 27.738353-30.674824v-0.286118l67.102118-5.406117h0.406588c17.618824 0 34.846118 15.781647 40.463059 33.355294l-2.138353 4.186353c1.822118-2.846118-0.421647-3.975529-0.421647-4.502588V206.320941c0-41.351529-26.608941-89.615059-69.285647-89.615059H504.470588a14.757647 14.757647 0 0 0-5.421176 1.099294c-0.346353-0.391529-0.752941-1.099294-1.084236-1.099294h-107.248941c-21.383529 0-40.568471 31.096471-40.56847 57.690353v605.364706c0 27.226353 27.151059 44.709647 56.214588 44.709647h200.282353c-3.343059 0-2.56-19.968 2.785882-29.741176l6.610824-11.294118c-5.270588-9.984-9.426824-18.642824-12.619294-27.738353l-10.480942-2.921412zM398.245647 282.352941h379.316706a7.544471 7.544471 0 1 1 0 15.058824H398.245647a7.544471 7.544471 0 1 1 0-15.058824z m0 135.529412H686.531765a7.544471 7.544471 0 1 1 0 15.058823H398.245647a7.544471 7.544471 0 1 1 0-15.058823z m0 105.411765h197.240471a7.544471 7.544471 0 1 1 0 15.058823H398.245647a7.544471 7.544471 0 1 1 0-15.058823z" ></path><path d="M335.058824 779.760941v-605.364706c0-13.733647 4.773647-28.069647 10.962823-40.357647L43.008 307.606588C6.686118 328.357647-5.662118 374.784 15.088941 411.105882l277.775059 485.707294a75.535059 75.535059 0 0 0 46.004706 35.493648c6.686118 1.822118 13.492706 2.710588 20.224 2.710588 13.010824 0 25.856-6.189176 37.421176-12.8l139.700706-82.672941h-129.882353c-37.933176-0.015059-71.273412-23.717647-71.273411-59.78353z m-25.705412-137.426823l-37.12 21.217882a7.559529 7.559529 0 0 1-10.360471-2.816 7.574588 7.574588 0 0 1 2.831059-10.345412l37.12-21.217882a7.574588 7.574588 0 1 1 7.529412 13.161412z m0.873412-131.192471l-94.313412 53.910588a7.649882 7.649882 0 0 1-10.360471-2.831059 7.604706 7.604706 0 0 1 2.831059-10.36047l94.313412-53.910588a7.589647 7.589647 0 1 1 7.529412 13.191529z m2.258823-155.768471L149.368471 448.617412a7.559529 7.559529 0 0 1-10.345412-2.816 7.559529 7.559529 0 0 1 2.816-10.345412l163.102117-93.244235a7.574588 7.574588 0 1 1 7.544471 13.161411z" ></path></symbol><symbol id="icon-avatar" viewBox="0 0 1024 1024"><path d="M410.156231 702.707207h212.242673c16.639103 0 30.116023-13.48194 30.116023-30.116023s-13.47692-30.116023-30.116023-30.116023H410.156231c-16.634083 0-30.116023 13.48194-30.116023 30.116023s13.48194 30.116023 30.116023 30.116023z" fill="#EB4548" ></path><path d="M999.597075 85.840705a20.042213 20.042213 0 0 0-28.258869-2.765655l-57.692262 47.397601C888.348485 54.781046 816.923317 0 732.824322 0c-80.339511 0-149.099411 49.992598-177.157506 120.464093H468.270117C440.22206 49.992598 371.46216 0 291.122649 0 207.028674 0 135.603506 54.781046 110.301027 130.472651L52.608765 83.07505a20.052252 20.052252 0 0 0-28.258868 2.760635 20.06731 20.06731 0 0 0 2.770674 28.258869L96.683565 171.239708c1.405414 1.154448 2.991525 1.862174 4.557558 2.579939a192.873051 192.873051 0 0 0-0.853287 16.910147c0 105.170172 85.564641 190.734813 190.734813 190.734813s190.734813-85.564641 190.734813-190.734813c0-10.274583-1.039003-20.278122-2.615074-30.116023h65.467215c-1.571053 9.837901-2.620094 19.84144-2.620094 30.116023 0 105.170172 85.564641 190.734813 190.734813 190.734813s190.734813-85.564641 190.734813-190.734813c0-5.706986-0.361392-11.333663-0.863326-16.915166 1.581091-0.712746 3.167202-1.415453 4.567597-2.57492l69.568014-57.155193a20.06731 20.06731 0 0 0 2.760635-28.24883zM291.122649 341.314929c-53.24011 0-100.010294-27.852302-126.793477-69.6684l-0.250967 0.190735a19.99202 19.99202 0 0 1-12.48811 4.366823 20.082368 20.082368 0 0 1-12.508189-35.787874l7.900437-6.294249A149.987834 149.987834 0 0 1 140.542533 190.734813c0-83.034895 67.545221-150.580116 150.580116-150.580115 27.174692 0 52.617712 7.328232 74.642564 19.976962l3.824735-3.036699a20.06731 20.06731 0 0 1 28.213694 3.212375c5.802354 7.308155 5.390768 17.206288 0.050193 24.333747C424.938179 111.881026 441.702765 149.385514 441.702765 190.734813c0 83.034895-67.545221 150.580116-150.580116 150.580116z m441.701673 0c-53.24011 0-100.010294-27.852302-126.793477-69.6684l-0.250967 0.190735a19.99202 19.99202 0 0 1-12.48811 4.366823 20.082368 20.082368 0 0 1-12.508189-35.787874l7.900437-6.294249A149.987834 149.987834 0 0 1 582.244206 190.734813c0-83.034895 67.545221-150.580116 150.580116-150.580115 27.174692 0 52.617712 7.328232 74.642564 19.976962l3.824734-3.036699a20.072329 20.072329 0 0 1 28.213695 3.212375c5.802354 7.308155 5.390768 17.206288 0.050193 24.333747 27.084343 27.234924 43.84893 64.74443 43.84893 106.09373 0 83.034895-67.545221 150.580116-150.580116 150.580116z" fill="#4F676D" ></path><path d="M394.586247 88.52605L164.329172 271.64151C191.112356 313.462627 237.87752 341.314929 291.122649 341.314929c83.034895 0 150.580116-67.545221 150.580116-150.580116 0-41.3493-16.764586-78.853787-43.84893-106.09373-1.008887 1.355221-1.882251 2.785732-3.262569 3.884967zM291.122649 40.154698c-83.034895 0-150.580116 67.545221-150.580116 150.580115 0 15.088128 2.293837 29.644205 6.43981 43.387151L365.770232 60.13166A149.41563 149.41563 0 0 0 291.132688 40.154698zM836.28792 88.52605l-230.257075 183.11546C632.814029 313.462627 679.579193 341.314929 732.824322 341.314929c83.034895 0 150.580116-67.545221 150.580116-150.580116 0-41.3493-16.764586-78.853787-43.84893-106.09373-1.008887 1.355221-1.882251 2.785732-3.262569 3.884967zM732.824322 40.154698c-83.034895 0-150.580116 67.545221-150.580116 150.580115 0 15.088128 2.293837 29.644205 6.43981 43.387151L807.471905 60.13166A149.41563 149.41563 0 0 0 732.834361 40.154698z" fill="#7BE5E4" ></path><path d="M839.555508 84.641083c5.340575-7.127459 5.75216-17.025592-0.050193-24.328727a20.072329 20.072329 0 0 0-28.213695-3.217395l-3.824734 3.036699-218.78287 173.990304-7.900437 6.28421a20.082368 20.082368 0 0 0 24.996299 31.43109l0.250967-0.195754 230.257075-183.11546c1.385337-1.099235 2.258702-2.529746 3.262569-3.884967zM397.853835 84.641083c5.340575-7.127459 5.75216-17.025592-0.050193-24.328727a20.06731 20.06731 0 0 0-28.213694-3.217395l-3.824735 3.036699-218.78287 173.990304-7.900437 6.28421a20.082368 20.082368 0 0 0 24.996299 31.43109l0.250967-0.195754L394.581227 88.52605c1.385337-1.099235 2.258702-2.529746 3.26257-3.884967z" fill="#B7F5F0" ></path><path d="M722.931209 441.701673H301.337c-88.661572 0-160.794467 72.057605-160.794467 160.61879v63.163339c0 5.541348 2.283798 10.83173 6.324365 14.626349a19.876575 19.876575 0 0 0 14.977702 5.410845c24.91097-1.606188 56.803839 4.657945 96.918382 14.405498a19.931788 19.931788 0 0 0 15.303959-2.444417 20.027155 20.027155 0 0 0 9.009711-12.618614c8.221674-36.189421 41.153546-62.465651 78.30166-62.465651h301.195367c37.158153 0 70.085005 26.261172 78.30166 62.455613a20.072329 20.072329 0 0 0 24.30865 15.06805c40.129601-9.732495 71.987334-15.986589 96.923401-14.400479 5.701967 0.411586 10.942155-1.616227 14.977702-5.410845a20.06731 20.06731 0 0 0 6.324365-14.626349V602.320463c0-88.561185-71.987334-160.61879-160.478248-160.61879zM792.96602 737.195073c-31.466225-24.604791-76.55995-13.40163-133.685026 0.783016-41.1937 10.22439-87.878556 21.814039-137.268834 21.81404s-96.070114-11.58965-137.263814-21.81404c-57.110019-14.184647-102.243899-25.377769-133.690046-0.783016-20.609399 16.117092-30.206371 44.034645-30.206372 87.858478 0 175.265216 152.236497 198.891236 301.160232 198.891236s301.160232-23.62602 301.160232-198.891236c0-43.818814-9.596973-71.741386-30.201352-87.858478z" fill="#89714D" ></path></symbol><symbol id="icon-user" viewBox="0 0 1024 1024"><path d="M984.615385 846.769231v43.323077c0 51.2-43.323077 94.523077-94.523077 94.523077H133.907692C82.707692 984.615385 39.384615 941.292308 39.384615 890.092308V846.769231c0-114.215385 133.907692-185.107692 259.938462-240.246154l11.815385-5.907692c9.846154-3.938462 19.692308-3.938462 29.538461 1.96923 51.2 33.476923 108.307692 51.2 169.353846 51.2s120.123077-19.692308 169.353846-51.2c9.846154-5.907692 19.692308-5.907692 29.538462-1.96923l11.815385 5.907692C850.707692 661.661538 984.615385 730.584615 984.615385 846.769231zM512 39.384615c129.969231 0 234.338462 116.184615 234.338462 259.938462S641.969231 559.261538 512 559.261538s-234.338462-116.184615-234.338462-259.938461S382.030769 39.384615 512 39.384615z" ></path></symbol><symbol id="icon-message" viewBox="0 0 1024 1024"><path d="M906.68 436.31v180.51c0 80.71-48.86 146.36-108.9 146.36H431.94v19.18c0 47.43 28.88 86.23 64.17 86.23h269.13l33.24 91.62 33.24-91.62h63.45c35.29 0 64.17-38.81 64.17-86.23V521c-0.01-42.17-22.83-77.33-52.66-84.69z" ></path><path d="M888.06 606.91V198.58c0-74.09-45.11-134.71-100.24-134.71H164.45c-55.13 0-100.24 60.62-100.24 134.71v408.33c0 74.09 45.11 134.71 100.24 134.71h99.11l51.92 143.13 51.92-143.13h420.42c55.18 0 100.24-60.62 100.24-134.71z m-632.5-169.46a53.35 53.35 0 1 1 53.35-53.35 53.35 53.35 0 0 1-53.35 53.35z m221.38 0a53.35 53.35 0 1 1 53.36-53.35 53.35 53.35 0 0 1-53.35 53.35z m221.39 0a53.35 53.35 0 1 1 53.35-53.35 53.35 53.35 0 0 1-53.35 53.35z" ></path></symbol></svg>', | ||
9 | + n = (n = document.getElementsByTagName("script"))[ | ||
10 | + n.length - 1 | ||
11 | + ].getAttribute("data-injectcss"), | ||
12 | + h = function(c, l) { | ||
13 | + l.parentNode.insertBefore(c, l); | ||
14 | + }; | ||
15 | + if (n && !c.__iconfont__svg__cssinject__) { | ||
16 | + c.__iconfont__svg__cssinject__ = !0; | ||
17 | + try { | ||
18 | + document.write( | ||
19 | + "<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>" | ||
20 | + ); | ||
21 | + } catch (c) { | ||
22 | + console && console.log(c); | ||
23 | + } | ||
24 | + } | ||
25 | + function s() { | ||
26 | + o || ((o = !0), a()); | ||
27 | + } | ||
28 | + function d() { | ||
29 | + try { | ||
30 | + e.documentElement.doScroll("left"); | ||
31 | + } catch (c) { | ||
32 | + return void setTimeout(d, 50); | ||
33 | + } | ||
34 | + s(); | ||
35 | + } | ||
36 | + (l = function() { | ||
37 | + var c, l; | ||
38 | + ((l = document.createElement("div")).innerHTML = i), | ||
39 | + (i = null), | ||
40 | + (c = l.getElementsByTagName("svg")[0]) && | ||
41 | + (c.setAttribute("aria-hidden", "true"), | ||
42 | + (c.style.position = "absolute"), | ||
43 | + (c.style.width = 0), | ||
44 | + (c.style.height = 0), | ||
45 | + (c.style.overflow = "hidden"), | ||
46 | + (l = c), | ||
47 | + (c = document.body).firstChild ? h(l, c.firstChild) : c.appendChild(l)); | ||
48 | + }), | ||
49 | + document.addEventListener | ||
50 | + ? ~["complete", "loaded", "interactive"].indexOf(document.readyState) | ||
51 | + ? setTimeout(l, 0) | ||
52 | + : ((t = function() { | ||
53 | + document.removeEventListener("DOMContentLoaded", t, !1), l(); | ||
54 | + }), | ||
55 | + document.addEventListener("DOMContentLoaded", t, !1)) | ||
56 | + : document.attachEvent && | ||
57 | + ((a = l), | ||
58 | + (e = c.document), | ||
59 | + (o = !1), | ||
60 | + d(), | ||
61 | + (e.onreadystatechange = function() { | ||
62 | + "complete" == e.readyState && ((e.onreadystatechange = null), s()); | ||
63 | + })); | ||
64 | +})(window); |
1 | +/* | ||
2 | +列表表格里 固定宽度 栏 | ||
3 | + */ | ||
4 | +const TABLE_FIX_WIDTH_PHONE = 'phone'; // 手机号 | ||
5 | +const TABLE_FIX_WIDTH_USERNAME = 'username'; // 名字 | ||
6 | +const TABLE_FIX_WIDTH_ID_CARD = 'idCard'; // 身份证 | ||
7 | +const TABLE_FIX_WIDTH_BANK_CARD = 'bankCard'; // 银行卡号码 | ||
8 | +const TABLE_FIX_WIDTH_LABEL = 'label'; // 标签 | ||
9 | +const TABLE_FIX_WIDTH_TIME = 'datetime'; // 时间 | ||
10 | +const TABLE_FIX_WIDTH_DATE = 'date'; // 日期 | ||
11 | + | ||
12 | +const tableFixWidths = { | ||
13 | + [TABLE_FIX_WIDTH_PHONE]: 120, | ||
14 | + [TABLE_FIX_WIDTH_USERNAME]: 88, | ||
15 | + [TABLE_FIX_WIDTH_LABEL]: 88, | ||
16 | + [TABLE_FIX_WIDTH_TIME]: 168, | ||
17 | + [TABLE_FIX_WIDTH_DATE]: 120, | ||
18 | + [TABLE_FIX_WIDTH_ID_CARD]: 180, | ||
19 | + [TABLE_FIX_WIDTH_BANK_CARD]: 190, | ||
20 | +}; | ||
21 | + | ||
22 | +export { | ||
23 | + TABLE_FIX_WIDTH_PHONE, | ||
24 | + TABLE_FIX_WIDTH_USERNAME, | ||
25 | + TABLE_FIX_WIDTH_LABEL, | ||
26 | + TABLE_FIX_WIDTH_TIME, | ||
27 | + TABLE_FIX_WIDTH_DATE, | ||
28 | + TABLE_FIX_WIDTH_ID_CARD, | ||
29 | + TABLE_FIX_WIDTH_BANK_CARD, | ||
30 | + tableFixWidths, | ||
31 | +}; |
rf-blog/code/admin/src/utils/request.js
0 → 100644
1 | +import axios from 'axios'; | ||
2 | +import qs from 'qs'; | ||
3 | +import { Message } from 'element-ui'; | ||
4 | +import { removeToken } from './auth'; | ||
5 | + | ||
6 | +axios.defaults.withCredentials = true; | ||
7 | + | ||
8 | +// 发送时 | ||
9 | +axios.interceptors.request.use( | ||
10 | + (config) => config, | ||
11 | + (err) => Promise.reject(err) | ||
12 | +); | ||
13 | + | ||
14 | +// 响应时 | ||
15 | +axios.interceptors.response.use( | ||
16 | + (response) => response, | ||
17 | + (err) => Promise.resolve(err.response) | ||
18 | +); | ||
19 | + | ||
20 | +// 检查状态码 | ||
21 | +function checkStatus(res) { | ||
22 | + if (res.status === 200 || res.status === 304) { | ||
23 | + return res.data; | ||
24 | + } | ||
25 | + // token过期清掉 | ||
26 | + if (res.status === 401) { | ||
27 | + removeToken(); | ||
28 | + Message({ | ||
29 | + message: res.data, | ||
30 | + type: 'error', | ||
31 | + duration: 2 * 1000, | ||
32 | + }); | ||
33 | + return { | ||
34 | + code: 401, | ||
35 | + msg: res.data || res.statusText, | ||
36 | + data: res.data, | ||
37 | + }; | ||
38 | + } | ||
39 | + return { | ||
40 | + code: 0, | ||
41 | + msg: res.data.msg || res.statusText, | ||
42 | + data: res.statusText, | ||
43 | + }; | ||
44 | +} | ||
45 | + | ||
46 | +// 检查CODE值 | ||
47 | +function checkCode(res) { | ||
48 | + if (res.code === 0) { | ||
49 | + Message({ | ||
50 | + message: res.msg, | ||
51 | + type: 'error', | ||
52 | + duration: 2 * 1000, | ||
53 | + }); | ||
54 | + throw new Error(res.msg); | ||
55 | + } | ||
56 | + return res; | ||
57 | +} | ||
58 | + | ||
59 | +const prefix = '/admin_api'; | ||
60 | +export default { | ||
61 | + get(url, params) { | ||
62 | + if (!url) return; | ||
63 | + return axios({ | ||
64 | + method: 'get', | ||
65 | + url: prefix + url, | ||
66 | + params, | ||
67 | + timeout: 30000, | ||
68 | + }) | ||
69 | + .then(checkStatus) | ||
70 | + .then(checkCode); | ||
71 | + }, | ||
72 | + post(url, data) { | ||
73 | + if (!url) return; | ||
74 | + return axios({ | ||
75 | + method: 'post', | ||
76 | + url: prefix + url, | ||
77 | + data: qs.stringify(data), | ||
78 | + timeout: 30000, | ||
79 | + }) | ||
80 | + .then(checkStatus) | ||
81 | + .then(checkCode); | ||
82 | + }, | ||
83 | + postFile(url, data) { | ||
84 | + if (!url) return; | ||
85 | + return axios({ | ||
86 | + method: 'post', | ||
87 | + url: prefix + url, | ||
88 | + data, | ||
89 | + }) | ||
90 | + .then(checkStatus) | ||
91 | + .then(checkCode); | ||
92 | + }, | ||
93 | +}; |
rf-blog/code/admin/src/utils/storage.js
0 → 100644
1 | +const ls = window.localStorage; | ||
2 | +const ss = window.sessionStorage; | ||
3 | + | ||
4 | +export const Cookie = { | ||
5 | + get(key) { | ||
6 | + let arr = document.cookie.split('; '); | ||
7 | + for (let i = 0; i < arr.length; i++) { | ||
8 | + let arr2 = arr[i].trim().split('='); | ||
9 | + if (arr2[0] == key) { | ||
10 | + return arr2[1]; | ||
11 | + } | ||
12 | + } | ||
13 | + return ''; | ||
14 | + }, | ||
15 | + set(key, value, day) { | ||
16 | + let setting = arguments[0]; | ||
17 | + if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { | ||
18 | + for (let i in setting) { | ||
19 | + let oDate = new Date(); | ||
20 | + oDate.setDate(oDate.getDate() + day); | ||
21 | + document.cookie = i + '=' + setting[i] + ';expires=' + oDate; | ||
22 | + } | ||
23 | + } else { | ||
24 | + let oDate = new Date(); | ||
25 | + oDate.setDate(oDate.getDate() + day); | ||
26 | + document.cookie = key + '=' + value + ';expires=' + oDate; | ||
27 | + } | ||
28 | + }, | ||
29 | + remove(key) { | ||
30 | + this.set(key, 1, -1); | ||
31 | + }, | ||
32 | +}; | ||
33 | + | ||
34 | +export const Local = { | ||
35 | + get(key) { | ||
36 | + if (key) return JSON.parse(ls.getItem(key)); | ||
37 | + return null; | ||
38 | + }, | ||
39 | + set(key, val) { | ||
40 | + const setting = arguments[0]; | ||
41 | + if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { | ||
42 | + for (const i in setting) { | ||
43 | + ls.setItem(i, JSON.stringify(setting[i])); | ||
44 | + } | ||
45 | + } else { | ||
46 | + ls.setItem(key, JSON.stringify(val)); | ||
47 | + } | ||
48 | + }, | ||
49 | + remove(key) { | ||
50 | + ls.removeItem(key); | ||
51 | + }, | ||
52 | + clear() { | ||
53 | + ls.clear(); | ||
54 | + }, | ||
55 | +}; | ||
56 | + | ||
57 | +export const Session = { | ||
58 | + get(key) { | ||
59 | + if (key) return JSON.parse(ss.getItem(key)); | ||
60 | + return null; | ||
61 | + }, | ||
62 | + set(key, val) { | ||
63 | + const setting = arguments[0]; | ||
64 | + if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { | ||
65 | + for (const i in setting) { | ||
66 | + ss.setItem(i, JSON.stringify(setting[i])); | ||
67 | + } | ||
68 | + } else { | ||
69 | + ss.setItem(key, JSON.stringify(val)); | ||
70 | + } | ||
71 | + }, | ||
72 | + remove(key) { | ||
73 | + ss.removeItem(key); | ||
74 | + }, | ||
75 | + clear() { | ||
76 | + ss.clear(); | ||
77 | + }, | ||
78 | +}; |
rf-blog/code/admin/src/views/Article/add.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="article-add"> | ||
3 | + <zp-page-edit :back="true" @back="$router.back()"> | ||
4 | + <div slot="header"> | ||
5 | + {{ header }} | ||
6 | + </div> | ||
7 | + | ||
8 | + <el-form | ||
9 | + :model="info" | ||
10 | + :rules="rules" | ||
11 | + ref="form" | ||
12 | + label-width="100px" | ||
13 | + class="form" | ||
14 | + > | ||
15 | + <el-form-item label="博客类型:" prop="type"> | ||
16 | + <el-select | ||
17 | + v-model="info.type" | ||
18 | + multiple | ||
19 | + clearable | ||
20 | + placeholder="请选择博客类型" | ||
21 | + class="block" | ||
22 | + > | ||
23 | + <el-option | ||
24 | + v-for="item in labelList" | ||
25 | + :key="item.label" | ||
26 | + :label="item.label" | ||
27 | + :value="item.label" | ||
28 | + > | ||
29 | + </el-option> | ||
30 | + </el-select> | ||
31 | + </el-form-item> | ||
32 | + <el-form-item label="文章标题:" prop="title"> | ||
33 | + <el-input type="text" v-model="info.title"></el-input> | ||
34 | + </el-form-item> | ||
35 | + <el-form-item label="文章描述:" prop="desc"> | ||
36 | + <el-input type="textarea" v-model="info.desc"></el-input> | ||
37 | + </el-form-item> | ||
38 | + <el-form-item label="文章封面:" prop="fileCoverImgUrl"> | ||
39 | + <zp-single-img-upload v-model="info.fileCoverImgUrl" :public="true"> | ||
40 | + </zp-single-img-upload> | ||
41 | + </el-form-item> | ||
42 | + <el-form-item label="文章内容:" prop="markdown" class="markdown"> | ||
43 | + <Markdown v-model="info.markdown"></Markdown> | ||
44 | + </el-form-item> | ||
45 | + <el-form-item label="级别:" prop="album"> | ||
46 | + <el-select | ||
47 | + v-model="info.level" | ||
48 | + placeholder="请选择级别" | ||
49 | + class="block" | ||
50 | + > | ||
51 | + <el-option | ||
52 | + v-for="item in [1, 2, 3, 4, 5, 6]" | ||
53 | + :key="item" | ||
54 | + :label="item" | ||
55 | + :value="item" | ||
56 | + ></el-option> | ||
57 | + </el-select> | ||
58 | + </el-form-item> | ||
59 | + <el-form-item label="来源:" prop="source"> | ||
60 | + <el-select | ||
61 | + v-model="info.source" | ||
62 | + placeholder="请选择来源" | ||
63 | + class="block" | ||
64 | + > | ||
65 | + <el-option | ||
66 | + v-for="item in sources" | ||
67 | + :key="item.id" | ||
68 | + :label="item.name" | ||
69 | + :value="item.id" | ||
70 | + ></el-option> | ||
71 | + </el-select> | ||
72 | + </el-form-item> | ||
73 | + <el-form-item label="Github:" prop="github"> | ||
74 | + <el-input type="text" v-model="info.github"></el-input> | ||
75 | + </el-form-item> | ||
76 | + <el-form-item label="Auth:" prop="auth"> | ||
77 | + <el-input type="text" v-model="info.auth"></el-input> | ||
78 | + </el-form-item> | ||
79 | + <el-form-item label="是否可见:" prop="isVisible" class="left-item"> | ||
80 | + <el-switch v-model="info.isVisible"></el-switch> | ||
81 | + </el-form-item> | ||
82 | + <el-form-item> | ||
83 | + <el-button | ||
84 | + type="primary" | ||
85 | + @click="handleSubmit('form')" | ||
86 | + :loading="loading" | ||
87 | + >立即创建</el-button | ||
88 | + > | ||
89 | + </el-form-item> | ||
90 | + </el-form> | ||
91 | + </zp-page-edit> | ||
92 | + </div> | ||
93 | +</template> | ||
94 | + | ||
95 | +<script> | ||
96 | +import { sources } from "src/constant/classify"; | ||
97 | +import { apiGetLabelList } from "src/api/label"; | ||
98 | +import { apiAddBlog } from "src/api/blog"; | ||
99 | +import Markdown from "components/markdown"; | ||
100 | +export default { | ||
101 | + name: "articleAdd", | ||
102 | + components: { Markdown }, | ||
103 | + computed: {}, | ||
104 | + data() { | ||
105 | + return { | ||
106 | + sources, | ||
107 | + header: "添加文章", | ||
108 | + labelList: [], | ||
109 | + info: { | ||
110 | + type: ["JavaScript"], | ||
111 | + title: "", | ||
112 | + desc: "", | ||
113 | + fileCoverImgUrl: "", | ||
114 | + html: "", | ||
115 | + markdown: "", | ||
116 | + level: 1, | ||
117 | + source: 1, | ||
118 | + github: "", | ||
119 | + auth: "", | ||
120 | + isVisible: true, | ||
121 | + releaseTime: new Date().getTime() + "", | ||
122 | + }, | ||
123 | + loading: false, | ||
124 | + rules: { | ||
125 | + type: [ | ||
126 | + { | ||
127 | + required: true, | ||
128 | + message: "请选择至少选择一个文章类型", | ||
129 | + trigger: "change", | ||
130 | + type: "array", | ||
131 | + }, | ||
132 | + ], | ||
133 | + title: [{ required: true, message: "请填写文章标题", trigger: "blur" }], | ||
134 | + desc: [{ required: true, message: "请填写文章描述", trigger: "blur" }], | ||
135 | + isVisible: [ | ||
136 | + { | ||
137 | + required: true, | ||
138 | + message: "请选择", | ||
139 | + trigger: "change", | ||
140 | + type: "boolean", | ||
141 | + }, | ||
142 | + ], | ||
143 | + fileCoverImgUrl: [ | ||
144 | + { required: true, message: "请上传文章封面", trigger: "change" }, | ||
145 | + ], | ||
146 | + }, | ||
147 | + }; | ||
148 | + }, | ||
149 | + created() { | ||
150 | + this.getLabelList(); | ||
151 | + }, | ||
152 | + methods: { | ||
153 | + handleSubmit(formName) { | ||
154 | + this.loading = true; | ||
155 | + if (!this.info.markdown) { | ||
156 | + this.$message.warning("请填写文章内容"); | ||
157 | + this.loading = false; | ||
158 | + return; | ||
159 | + } | ||
160 | + this.$refs[formName].validate((valid) => { | ||
161 | + if (valid) { | ||
162 | + this.info.html = this.info.markdown; | ||
163 | + this.handleAddBlog(); | ||
164 | + } else { | ||
165 | + this.loading = false; | ||
166 | + return false; | ||
167 | + } | ||
168 | + }); | ||
169 | + }, | ||
170 | + handleAddBlog() { | ||
171 | + return apiAddBlog(this.info) | ||
172 | + .then((res) => { | ||
173 | + this.$message.success("新增文章成功"); | ||
174 | + this.$router.push("/article/list"); | ||
175 | + }) | ||
176 | + .catch((err) => { | ||
177 | + console.log(err); | ||
178 | + this.$message.info("新增文章失败"); | ||
179 | + }) | ||
180 | + .finally(() => { | ||
181 | + this.loading = false; | ||
182 | + }); | ||
183 | + }, | ||
184 | + getLabelList() { | ||
185 | + let params = { | ||
186 | + pageindex: 1, | ||
187 | + pagesize: 50, | ||
188 | + }; | ||
189 | + return apiGetLabelList(params) | ||
190 | + .then((res) => { | ||
191 | + let { list } = res.data; | ||
192 | + this.labelList = list; | ||
193 | + }) | ||
194 | + .catch((err) => { | ||
195 | + console.log(err); | ||
196 | + }) | ||
197 | + .finally(() => { | ||
198 | + }); | ||
199 | + }, | ||
200 | + }, | ||
201 | +}; | ||
202 | +</script> | ||
203 | + | ||
204 | + | ||
205 | +<style lang="less" scoped> | ||
206 | +/deep/ .markdown { | ||
207 | + .el-form-item__content { | ||
208 | + width: 1400px; | ||
209 | + } | ||
210 | +} | ||
211 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<template> | ||
2 | + <div class="article-edit" v-show="hasLoad"> | ||
3 | + <zp-page-edit :back="true" @back="$router.back()"> | ||
4 | + <div slot="header"> | ||
5 | + {{ header }} | ||
6 | + </div> | ||
7 | + | ||
8 | + <el-form | ||
9 | + :model="info" | ||
10 | + :rules="rules" | ||
11 | + ref="form" | ||
12 | + label-width="100px" | ||
13 | + class="form" | ||
14 | + > | ||
15 | + <el-form-item label="文章类型:" prop="type"> | ||
16 | + <el-select | ||
17 | + v-model="info.type" | ||
18 | + multiple | ||
19 | + clearable | ||
20 | + placeholder="请选择文章类型" | ||
21 | + class="block" | ||
22 | + > | ||
23 | + <el-option | ||
24 | + v-for="item in labelList" | ||
25 | + :key="item.label" | ||
26 | + :label="item.label" | ||
27 | + :value="item.label" | ||
28 | + > | ||
29 | + </el-option> | ||
30 | + </el-select> | ||
31 | + </el-form-item> | ||
32 | + <el-form-item label="文章标题:" prop="title"> | ||
33 | + <el-input type="text" v-model="info.title"></el-input> | ||
34 | + </el-form-item> | ||
35 | + <el-form-item label="文章描述:" prop="desc"> | ||
36 | + <el-input type="textarea" v-model="info.desc"></el-input> | ||
37 | + </el-form-item> | ||
38 | + <el-form-item label="文章封面:" prop="fileCoverImgUrl"> | ||
39 | + <zp-single-img-upload v-model="info.fileCoverImgUrl" :public="true"> | ||
40 | + </zp-single-img-upload> | ||
41 | + </el-form-item> | ||
42 | + <el-form-item | ||
43 | + label="文章内容:" | ||
44 | + prop="markdown" | ||
45 | + v-if="info.markdown" | ||
46 | + class="markdown" | ||
47 | + > | ||
48 | + <Markdown v-model="info.markdown"></Markdown> | ||
49 | + </el-form-item> | ||
50 | + <el-form-item label="级别:" prop="album"> | ||
51 | + <el-select | ||
52 | + v-model="info.level" | ||
53 | + placeholder="请选择级别" | ||
54 | + class="block" | ||
55 | + > | ||
56 | + <el-option | ||
57 | + v-for="item in [1, 2, 3, 4, 5, 6]" | ||
58 | + :key="item" | ||
59 | + :label="item" | ||
60 | + :value="item" | ||
61 | + > | ||
62 | + </el-option> | ||
63 | + </el-select> | ||
64 | + </el-form-item> | ||
65 | + <el-form-item label="来源:" prop="source"> | ||
66 | + <el-select | ||
67 | + v-model="info.source" | ||
68 | + placeholder="请选择来源" | ||
69 | + class="block" | ||
70 | + > | ||
71 | + <el-option | ||
72 | + v-for="item in sources" | ||
73 | + :key="item.id" | ||
74 | + :label="item.name" | ||
75 | + :value="item.id" | ||
76 | + ></el-option> | ||
77 | + </el-select> | ||
78 | + </el-form-item> | ||
79 | + <el-form-item label="Github:" prop="github"> | ||
80 | + <el-input type="text" v-model="info.github"></el-input> | ||
81 | + </el-form-item> | ||
82 | + <el-form-item label="Auth:" prop="auth"> | ||
83 | + <el-input type="text" v-model="info.auth"></el-input> | ||
84 | + </el-form-item> | ||
85 | + <el-form-item label="是否可见:" prop="isVisible" class="left-item"> | ||
86 | + <el-switch v-model="info.isVisible"></el-switch> | ||
87 | + </el-form-item> | ||
88 | + <el-form-item> | ||
89 | + <el-button | ||
90 | + type="primary" | ||
91 | + @click="handleSubmit('form')" | ||
92 | + :loading="loading" | ||
93 | + >更新</el-button | ||
94 | + > | ||
95 | + </el-form-item> | ||
96 | + </el-form> | ||
97 | + </zp-page-edit> | ||
98 | + </div> | ||
99 | +</template> | ||
100 | + | ||
101 | +<script> | ||
102 | +import { apiUpdateBlog, apiGetBlogDetail } from "src/api/blog"; | ||
103 | +import { apiGetLabelList } from "src/api/label"; | ||
104 | +import { sources } from "src/constant/classify"; | ||
105 | +import Markdown from "components/markdown"; | ||
106 | +export default { | ||
107 | + name: "articleEdit", | ||
108 | + components: { Markdown }, | ||
109 | + props: {}, | ||
110 | + computed: {}, | ||
111 | + data() { | ||
112 | + return { | ||
113 | + sources, | ||
114 | + header: "文章编辑", | ||
115 | + loading: false, | ||
116 | + hasLoad: false, | ||
117 | + labelList: [], | ||
118 | + info: {}, | ||
119 | + id: "", // 文章id | ||
120 | + rules: { | ||
121 | + type: [ | ||
122 | + { | ||
123 | + required: true, | ||
124 | + message: "请选择至少选择一个文章类型", | ||
125 | + trigger: "change", | ||
126 | + type: "array", | ||
127 | + }, | ||
128 | + ], | ||
129 | + title: [{ required: true, message: "请填写文章标题", trigger: "blur" }], | ||
130 | + desc: [{ required: true, message: "请填写文章描述", trigger: "blur" }], | ||
131 | + markdown: [ | ||
132 | + { required: true, message: "请填写文章内容", trigger: "blur" }, | ||
133 | + ], | ||
134 | + fileCoverImgUrl: [ | ||
135 | + { required: true, message: "请上传文章封面", trigger: "change" }, | ||
136 | + ], | ||
137 | + }, | ||
138 | + }; | ||
139 | + }, | ||
140 | + watch: {}, | ||
141 | + async created() { | ||
142 | + this.id = this.$route.query["id"]; | ||
143 | + await this.getLabelList(); | ||
144 | + await this.getBlogDetail(); | ||
145 | + | ||
146 | + }, | ||
147 | + mounted() { }, | ||
148 | + beforeDestroy() { }, | ||
149 | + methods: { | ||
150 | + handleSubmit(formName) { | ||
151 | + this.loading = true; | ||
152 | + this.$refs[formName].validate((valid) => { | ||
153 | + if (valid) { | ||
154 | + this.info.releaseTime = new Date().getTime() + ""; | ||
155 | + this.info.html = this.info.markdown; | ||
156 | + this.handleUpdateBlog(); | ||
157 | + } else { | ||
158 | + console.log("error submit!!"); | ||
159 | + return false; | ||
160 | + } | ||
161 | + }); | ||
162 | + }, | ||
163 | + getBlogDetail() { | ||
164 | + return apiGetBlogDetail({ _id: this.id }) | ||
165 | + .then((res) => { | ||
166 | + this.info = res.data; | ||
167 | + }) | ||
168 | + .catch((err) => { | ||
169 | + console.log(err); | ||
170 | + this.$message.info("获取文章详情失败"); | ||
171 | + }) | ||
172 | + .finally(() => { | ||
173 | + this.hasLoad = true; | ||
174 | + }); | ||
175 | + }, | ||
176 | + handleUpdateBlog() { | ||
177 | + return apiUpdateBlog(this.info) | ||
178 | + .then((res) => { | ||
179 | + this.$message.success("修改文章成功"); | ||
180 | + this.$router.push("/article/list"); | ||
181 | + }) | ||
182 | + .catch((err) => { | ||
183 | + console.log(err); | ||
184 | + this.$message.info("修改文章失败"); | ||
185 | + }) | ||
186 | + .finally(() => { | ||
187 | + this.loading = false; | ||
188 | + }); | ||
189 | + }, | ||
190 | + getLabelList() { | ||
191 | + let params = { | ||
192 | + pageindex: 1, | ||
193 | + pagesize: 50, | ||
194 | + }; | ||
195 | + return apiGetLabelList(params) | ||
196 | + .then((res) => { | ||
197 | + let { list } = res.data; | ||
198 | + this.labelList = list; | ||
199 | + }) | ||
200 | + .catch((err) => { | ||
201 | + console.log(err); | ||
202 | + }) | ||
203 | + .finally(() => { | ||
204 | + }); | ||
205 | + }, | ||
206 | + }, | ||
207 | +}; | ||
208 | +</script> | ||
209 | + | ||
210 | +<style lang="less" scoped> | ||
211 | +/deep/ .markdown { | ||
212 | + .el-form-item__content { | ||
213 | + width: 1400px; | ||
214 | + } | ||
215 | +} | ||
216 | +</style> |
1 | +<template> | ||
2 | + <div class="article-list"> | ||
3 | + <zp-page-list> | ||
4 | + <template v-slot:header> | ||
5 | + <span>文章列表</span> | ||
6 | + </template> | ||
7 | + <!-- filter start --> | ||
8 | + <div slot="filter"> | ||
9 | + <el-form | ||
10 | + ref="searchForm" | ||
11 | + class="zp-search-form" | ||
12 | + :inline="true" | ||
13 | + :model="searchForm" | ||
14 | + > | ||
15 | + <el-form-item label="关键词:" prop="keyword"> | ||
16 | + <el-input | ||
17 | + placeholder="请输入类型、标题" | ||
18 | + v-model="searchForm.keyword" | ||
19 | + @keydown.enter.native="getBlogList(true)" | ||
20 | + ></el-input> | ||
21 | + </el-form-item> | ||
22 | + <el-button type="primary" @click="getBlogList(true)"> | ||
23 | + 查询 | ||
24 | + </el-button> | ||
25 | + </el-form> | ||
26 | + </div> | ||
27 | + <!-- filter end --> | ||
28 | + <!-- list start --> | ||
29 | + <div slot="list"> | ||
30 | + <zp-table-list | ||
31 | + ref="sensitiveBehaviorRecordList" | ||
32 | + :loading="listLoading" | ||
33 | + :source="blogList" | ||
34 | + :count="pageInfo.total" | ||
35 | + :columns="columns" | ||
36 | + @size-change="handleSizeChange" | ||
37 | + @current-change="handleCurrentChange" | ||
38 | + > | ||
39 | + <template slot="_id" slot-scope="scope"> | ||
40 | + <el-button | ||
41 | + icon="el-icon-document-copy" | ||
42 | + type="primary" | ||
43 | + size="mini" | ||
44 | + class="clipboardBtn" | ||
45 | + :data-clipboard-text="scope.row._id" | ||
46 | + @click="handleCopyId" | ||
47 | + >复制 | ||
48 | + </el-button> | ||
49 | + </template> | ||
50 | + <template slot="isVisible" slot-scope="scope"> | ||
51 | + {{ scope.row.isVisible ? "是" : "否" }} | ||
52 | + </template> | ||
53 | + <template slot="fileCoverImgUrl" slot-scope="scope"> | ||
54 | + <img | ||
55 | + :src="scope.row.fileCoverImgUrl" | ||
56 | + style="width: 60px; object-fit: contain" | ||
57 | + /> | ||
58 | + </template> | ||
59 | + <template slot="source" slot-scope="scope"> | ||
60 | + {{ | ||
61 | + scope.row.source === 1 | ||
62 | + ? "原创" | ||
63 | + : scope.row.source === 2 | ||
64 | + ? "转载" | ||
65 | + : "翻译" | ||
66 | + }} | ||
67 | + </template> | ||
68 | + <template slot="releaseTime" slot-scope="scope"> | ||
69 | + {{ scope.row.releaseTime | formatTime("yyyy-MM-dd hh:mm:ss") }} | ||
70 | + </template> | ||
71 | + <template slot="type" slot-scope="scope"> | ||
72 | + <el-tag | ||
73 | + class="tag" | ||
74 | + type="primary" | ||
75 | + close-transition | ||
76 | + v-for="(tag, index) in scope.row.type" | ||
77 | + :key="index" | ||
78 | + >{{ tag }}</el-tag | ||
79 | + > | ||
80 | + </template> | ||
81 | + <template slot="operate" slot-scope="scope"> | ||
82 | + <el-button size="mini" type="primary" @click="handleEdit(scope.row)" | ||
83 | + >编辑</el-button | ||
84 | + > | ||
85 | + <el-button | ||
86 | + size="mini" | ||
87 | + type="danger" | ||
88 | + @click="handleDelete(scope.row)" | ||
89 | + >删除</el-button | ||
90 | + > | ||
91 | + </template> | ||
92 | + </zp-table-list> | ||
93 | + </div> | ||
94 | + <!-- list end --> | ||
95 | + </zp-page-list> | ||
96 | + </div> | ||
97 | +</template> | ||
98 | + | ||
99 | +<script> | ||
100 | +import Clipboard from "clipboard"; | ||
101 | +import { apiGetBlogList, apiDelBlog } from "src/api/blog"; | ||
102 | +import { apiDelUploadImg } from "src/api/upload"; | ||
103 | +export default { | ||
104 | + name: "articleList", | ||
105 | + components: {}, | ||
106 | + computed: {}, | ||
107 | + data() { | ||
108 | + return { | ||
109 | + listLoading: false, | ||
110 | + searchForm: { | ||
111 | + keyword: "", | ||
112 | + }, | ||
113 | + blogList: [], | ||
114 | + columns: [ | ||
115 | + { | ||
116 | + label: "_id", | ||
117 | + prop: "_id", | ||
118 | + slot: "_id", | ||
119 | + }, | ||
120 | + { | ||
121 | + label: "类型", | ||
122 | + prop: "type", | ||
123 | + slot: "type", | ||
124 | + showTooltip: true, | ||
125 | + width: "120", | ||
126 | + }, | ||
127 | + { | ||
128 | + label: "标题", | ||
129 | + prop: "title", | ||
130 | + width: "160", | ||
131 | + }, | ||
132 | + { | ||
133 | + label: "描述", | ||
134 | + prop: "desc", | ||
135 | + width: "160", | ||
136 | + showTooltip: true, | ||
137 | + }, | ||
138 | + { | ||
139 | + label: "封面", | ||
140 | + slot: "fileCoverImgUrl", | ||
141 | + prop: "fileCoverImgUrl", | ||
142 | + }, | ||
143 | + { | ||
144 | + label: "来源", | ||
145 | + prop: "source", | ||
146 | + slot: "source", | ||
147 | + }, | ||
148 | + { | ||
149 | + label: "级别", | ||
150 | + prop: "level", | ||
151 | + }, | ||
152 | + { | ||
153 | + label: "发布时间", | ||
154 | + prop: "releaseTime", | ||
155 | + slot: "releaseTime", | ||
156 | + width: "160", | ||
157 | + }, | ||
158 | + { | ||
159 | + label: "是否可见", | ||
160 | + prop: "isVisible", | ||
161 | + slot: "isVisible", | ||
162 | + }, | ||
163 | + { | ||
164 | + label: "作者", | ||
165 | + prop: "auth", | ||
166 | + }, | ||
167 | + { | ||
168 | + label: "浏览量", | ||
169 | + prop: "pv", | ||
170 | + }, | ||
171 | + { | ||
172 | + label: "点赞数", | ||
173 | + prop: "likes", | ||
174 | + }, | ||
175 | + { | ||
176 | + label: "评论数", | ||
177 | + prop: "comments", | ||
178 | + }, | ||
179 | + { | ||
180 | + label: "操作", | ||
181 | + slot: "operate", | ||
182 | + width: "150", | ||
183 | + }, | ||
184 | + ], | ||
185 | + }; | ||
186 | + }, | ||
187 | + created() { | ||
188 | + this.requestPageData = this.getBlogList; | ||
189 | + this.getBlogList(); | ||
190 | + }, | ||
191 | + mounted() { }, | ||
192 | + methods: { | ||
193 | + handleCopyId() { | ||
194 | + let clipboard = new Clipboard(".clipboardBtn"); | ||
195 | + clipboard.on("success", (e) => { | ||
196 | + this.$message.success("复制成功"); | ||
197 | + clipboard.destroy(); | ||
198 | + }); | ||
199 | + clipboard.on("error", (e) => { | ||
200 | + this.$message.error("该浏览器不支持自动复制"); | ||
201 | + clipboard.destroy(); | ||
202 | + }); | ||
203 | + }, | ||
204 | + getBlogList() { | ||
205 | + let params = { | ||
206 | + keyword: this.searchForm.keyword, | ||
207 | + pageindex: this.pageInfo.pageNum, | ||
208 | + pagesize: this.pageInfo.pageSize, | ||
209 | + }; | ||
210 | + this.listLoading = true; | ||
211 | + return apiGetBlogList(params) | ||
212 | + .then((res) => { | ||
213 | + let { list, total } = res.data; | ||
214 | + this.blogList = list; | ||
215 | + this.pageInfo.total = total; | ||
216 | + }) | ||
217 | + .catch((err) => { | ||
218 | + this.listLoading = false; | ||
219 | + console.log(err); | ||
220 | + }) | ||
221 | + .finally(() => { | ||
222 | + this.listLoading = false; | ||
223 | + }); | ||
224 | + }, | ||
225 | + handleDeleteBlog(id) { | ||
226 | + return apiDelBlog({ id }) | ||
227 | + .then((res) => { | ||
228 | + this.$message.success("删除成功"); | ||
229 | + this.getBlogList(true); | ||
230 | + }) | ||
231 | + .catch((err) => { | ||
232 | + console.log(err); | ||
233 | + this.$message.info("删除失败"); | ||
234 | + }) | ||
235 | + .finally(() => { }); | ||
236 | + }, | ||
237 | + // 删除本地图片 | ||
238 | + handleDeleteImg(fileName) { | ||
239 | + return apiDelUploadImg({ fileName }) | ||
240 | + .then((res) => { }) | ||
241 | + .catch((err) => { | ||
242 | + console.log(err); | ||
243 | + }); | ||
244 | + }, | ||
245 | + handleDelete(row) { | ||
246 | + this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", { | ||
247 | + confirmButtonText: "确定", | ||
248 | + cancelButtonText: "取消", | ||
249 | + type: "warning", | ||
250 | + }) | ||
251 | + .then(async () => { | ||
252 | + await this.handleDeleteBlog(row._id); | ||
253 | + let index = row.fileCoverImgUrl.lastIndexOf('/'); | ||
254 | + let fileName = row.fileCoverImgUrl.substring(index + 1); | ||
255 | + this.handleDeleteImg(fileName); | ||
256 | + }) | ||
257 | + .catch(() => { | ||
258 | + this.$message.info("已取消删除"); | ||
259 | + }); | ||
260 | + }, | ||
261 | + handleEdit(row) { | ||
262 | + this.$router.push({ path: "edit", query: { id: row._id } }); | ||
263 | + }, | ||
264 | + }, | ||
265 | +}; | ||
266 | +</script> | ||
267 | + | ||
268 | + | ||
269 | +<style lang="less" scoped> | ||
270 | +.tag { | ||
271 | + margin: 0 10px; | ||
272 | +} | ||
273 | +</style> |
rf-blog/code/admin/src/views/Home/index.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="home-container">home</div> | ||
3 | +</template> | ||
4 | + | ||
5 | +<script> | ||
6 | +export default { | ||
7 | + name: "home", | ||
8 | + components: {}, | ||
9 | + computed: {}, | ||
10 | + data() { | ||
11 | + return {}; | ||
12 | + }, | ||
13 | + created() {}, | ||
14 | + mounted() {}, | ||
15 | + methods: {}, | ||
16 | +}; | ||
17 | +</script> | ||
18 | + | ||
19 | + | ||
20 | +<style lang="less" scoped> | ||
21 | +</style> |
rf-blog/code/admin/src/views/Label/add.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="label-add"> | ||
3 | + <zp-page-edit :back="true" @back="$router.back()"> | ||
4 | + <div slot="header"> | ||
5 | + {{ header }} | ||
6 | + </div> | ||
7 | + <el-form | ||
8 | + :model="info" | ||
9 | + :rules="rules" | ||
10 | + ref="form" | ||
11 | + label-width="100px" | ||
12 | + class="form" | ||
13 | + > | ||
14 | + <el-form-item label="标签:" prop="label"> | ||
15 | + <el-input type="text" v-model="info.label"></el-input> | ||
16 | + </el-form-item> | ||
17 | + <el-form-item label="背景色:" prop="bgColor"> | ||
18 | + <el-input readonly :value="currentColor"></el-input> | ||
19 | + </el-form-item> | ||
20 | + <el-form-item> | ||
21 | + <sketch-picker | ||
22 | + v-model="currentColor" | ||
23 | + @input="colorValueChange" | ||
24 | + ></sketch-picker> | ||
25 | + </el-form-item> | ||
26 | + <el-form-item label="预览:" v-if="info.label"> | ||
27 | + <div class="label-box" :style="{ backgroundColor: currentColor }"> | ||
28 | + {{ info.label }} | ||
29 | + </div> | ||
30 | + </el-form-item> | ||
31 | + <el-form-item> | ||
32 | + <el-button | ||
33 | + type="primary" | ||
34 | + @click="handleSubmit('form')" | ||
35 | + :loading="loading" | ||
36 | + >立即创建</el-button | ||
37 | + > | ||
38 | + </el-form-item> | ||
39 | + </el-form> | ||
40 | + </zp-page-edit> | ||
41 | + </div> | ||
42 | +</template> | ||
43 | + | ||
44 | +<script> | ||
45 | +import { Sketch } from 'vue-color' | ||
46 | +import { apiAddLabel } from "src/api/label"; | ||
47 | +export default { | ||
48 | + name: "permissionAdd", | ||
49 | + components: { | ||
50 | + 'sketch-picker': Sketch | ||
51 | + }, | ||
52 | + data() { | ||
53 | + return { | ||
54 | + header: "添加标签", | ||
55 | + info: { | ||
56 | + label: "", | ||
57 | + bgColor: "rgba(70, 70, 70, 0.9)", | ||
58 | + }, | ||
59 | + currentColor: 'rgba(70, 70, 70, 0.9)', | ||
60 | + loading: false, | ||
61 | + rules: { | ||
62 | + label: [ | ||
63 | + { required: true, message: "请填写标签", trigger: "blur" }, | ||
64 | + ], | ||
65 | + bgColor: [{ required: true, message: "请填写背景色", trigger: "blur" }], | ||
66 | + }, | ||
67 | + }; | ||
68 | + }, | ||
69 | + methods: { | ||
70 | + handleSubmit(formName) { | ||
71 | + this.loading = true; | ||
72 | + this.$refs[formName].validate(async (valid) => { | ||
73 | + if (valid) { | ||
74 | + return apiAddLabel(this.info) | ||
75 | + .then((res) => { | ||
76 | + this.$message.success("新增成功"); | ||
77 | + this.$router.push("/label/list"); | ||
78 | + }) | ||
79 | + .catch((err) => { | ||
80 | + console.log(err); | ||
81 | + this.$message.info("新增失败"); | ||
82 | + }) | ||
83 | + .finally(() => { | ||
84 | + this.loading = false; | ||
85 | + }); | ||
86 | + } else { | ||
87 | + console.log("error submit!!"); | ||
88 | + this.loading = false; | ||
89 | + return false; | ||
90 | + } | ||
91 | + }); | ||
92 | + }, | ||
93 | + // 颜色值改变事件处理 | ||
94 | + colorValueChange(fmtObj) { | ||
95 | + // 取颜色对象的 rgba 值 | ||
96 | + const { r, g, b, a } = fmtObj.rgba; | ||
97 | + this.currentColor = `rgba(${r}, ${g}, ${b}, ${a})`; | ||
98 | + this.info.bgColor = this.currentColor; | ||
99 | + } | ||
100 | + }, | ||
101 | +}; | ||
102 | +</script> | ||
103 | +<style lang="less" scoped> | ||
104 | +.label-box { | ||
105 | + color: #fff; | ||
106 | + border-radius: 12px; | ||
107 | + font-size: 14px; | ||
108 | + text-align: center; | ||
109 | + max-width: 150px; | ||
110 | +} | ||
111 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<template> | ||
2 | + <zp-dialog | ||
3 | + title="标签编辑" | ||
4 | + :visible.sync="dialogTableVisible" | ||
5 | + @close="close" | ||
6 | + width="480px" | ||
7 | + :destroy-on-close="true" | ||
8 | + v-bind="$attrs" | ||
9 | + v-on="$listeners" | ||
10 | + > | ||
11 | + <el-form :model="info" :rules="rules" ref="form"> | ||
12 | + <el-form-item label="标签:" prop="label"> | ||
13 | + <el-input type="text" v-model="info.label"></el-input> | ||
14 | + </el-form-item> | ||
15 | + <el-form-item label="背景色:" prop="bgColor"> | ||
16 | + <el-input readonly :value="currentColor"></el-input> | ||
17 | + </el-form-item> | ||
18 | + <el-form-item style="margin-left: 80px"> | ||
19 | + <sketch-picker | ||
20 | + v-model="currentColor" | ||
21 | + @input="colorValueChange" | ||
22 | + ></sketch-picker> | ||
23 | + </el-form-item> | ||
24 | + <el-form-item label="预览:" v-if="info.label"> | ||
25 | + <div class="label-box" :style="{ backgroundColor: currentColor }"> | ||
26 | + {{ info.label }} | ||
27 | + </div> | ||
28 | + </el-form-item> | ||
29 | + </el-form> | ||
30 | + <div slot="footer" class="dialog-footer"> | ||
31 | + <el-button @click="close">取消</el-button> | ||
32 | + <el-button | ||
33 | + type="primary" | ||
34 | + :loading="loading" | ||
35 | + @click="handleSubmit('form')" | ||
36 | + > | ||
37 | + 确定 | ||
38 | + </el-button> | ||
39 | + </div> | ||
40 | + </zp-dialog> | ||
41 | +</template> | ||
42 | + | ||
43 | +<script> | ||
44 | +import { Sketch } from 'vue-color' | ||
45 | +import { apiUpdateLabel } from "src/api/label"; | ||
46 | +export default { | ||
47 | + props: ["info"], | ||
48 | + components: { | ||
49 | + 'sketch-picker': Sketch | ||
50 | + }, | ||
51 | + data() { | ||
52 | + return { | ||
53 | + loading: false, | ||
54 | + dialogTableVisible: true, | ||
55 | + currentColor: '', | ||
56 | + rules: { | ||
57 | + label: [ | ||
58 | + { required: true, message: "请填写标签名", trigger: "blur" }, | ||
59 | + ], | ||
60 | + bgColor: [{ required: true, message: "请填写背景色", trigger: "blur" }], | ||
61 | + }, | ||
62 | + }; | ||
63 | + }, | ||
64 | + created() { | ||
65 | + this.currentColor = this.info.bgColor; | ||
66 | + }, | ||
67 | + methods: { | ||
68 | + // 颜色值改变事件处理 | ||
69 | + colorValueChange(fmtObj) { | ||
70 | + // 取颜色对象的 rgba 值 | ||
71 | + const { r, g, b, a } = fmtObj.rgba; | ||
72 | + this.currentColor = `rgba(${r}, ${g}, ${b}, ${a})`; | ||
73 | + this.info.bgColor = this.currentColor; | ||
74 | + }, | ||
75 | + close() { | ||
76 | + this.$emit("close"); | ||
77 | + }, | ||
78 | + handleSubmit(formName) { | ||
79 | + this.loading = true; | ||
80 | + this.$refs[formName].validate(async (valid) => { | ||
81 | + if (valid) { | ||
82 | + return apiUpdateLabel(this.info) | ||
83 | + .then((res) => { | ||
84 | + this.$message.success("编辑成功"); | ||
85 | + this.$emit("close"); | ||
86 | + }) | ||
87 | + .catch((err) => { | ||
88 | + console.log(err); | ||
89 | + this.$message.info("编辑失败"); | ||
90 | + }) | ||
91 | + .finally(() => { | ||
92 | + this.loading = false; | ||
93 | + }); | ||
94 | + | ||
95 | + } else { | ||
96 | + console.log("error submit!!"); | ||
97 | + this.loading = false; | ||
98 | + return false; | ||
99 | + } | ||
100 | + }); | ||
101 | + }, | ||
102 | + }, | ||
103 | +}; | ||
104 | +</script> | ||
105 | + | ||
106 | + | ||
107 | +<style lang="less" scoped> | ||
108 | +.el-form { | ||
109 | + .el-form-item { | ||
110 | + display: flex; | ||
111 | + align-items: center; | ||
112 | + /deep/ .el-form-item__label { | ||
113 | + min-width: 80px; | ||
114 | + } | ||
115 | + /deep/.el-form-item__content { | ||
116 | + margin-left: 0 !important; | ||
117 | + width: 260px; | ||
118 | + .el-select { | ||
119 | + width: 260px; | ||
120 | + } | ||
121 | + } | ||
122 | + } | ||
123 | +} | ||
124 | +.label-box { | ||
125 | + color: #fff; | ||
126 | + border-radius: 12px; | ||
127 | + font-size: 14px; | ||
128 | + text-align: center; | ||
129 | + max-width: 150px; | ||
130 | +} | ||
131 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/admin/src/views/Label/list.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="label-list"> | ||
3 | + <zp-page-list> | ||
4 | + <template v-slot:header> | ||
5 | + <span>标签列表</span> | ||
6 | + </template> | ||
7 | + <!-- filter start --> | ||
8 | + <div slot="filter"> | ||
9 | + <el-form | ||
10 | + ref="searchForm" | ||
11 | + class="zp-search-form" | ||
12 | + :inline="true" | ||
13 | + :model="searchForm" | ||
14 | + > | ||
15 | + <el-form-item label="关键词:" prop="keyword"> | ||
16 | + <el-input | ||
17 | + placeholder="请输入标签、背景色" | ||
18 | + v-model="searchForm.keyword" | ||
19 | + @keydown.enter.native="getLabelList" | ||
20 | + ></el-input> | ||
21 | + </el-form-item> | ||
22 | + <el-button type="primary" @click="getLabelList"> 查询 </el-button> | ||
23 | + </el-form> | ||
24 | + </div> | ||
25 | + <!-- filter end --> | ||
26 | + <!-- list start --> | ||
27 | + <div slot="list"> | ||
28 | + <zp-table-list | ||
29 | + :loading="listLoading" | ||
30 | + :source="labelList" | ||
31 | + :count="pageInfo.total" | ||
32 | + :columns="columns" | ||
33 | + @size-change="handleSizeChange" | ||
34 | + @current-change="handleCurrentChange" | ||
35 | + > | ||
36 | + <template slot="_id" slot-scope="scope"> | ||
37 | + <el-button | ||
38 | + icon="el-icon-document-copy" | ||
39 | + type="primary" | ||
40 | + size="mini" | ||
41 | + class="clipboardBtn" | ||
42 | + :data-clipboard-text="scope.row._id" | ||
43 | + @click="handleCopyId" | ||
44 | + >复制 | ||
45 | + </el-button> | ||
46 | + </template> | ||
47 | + <template slot="label" slot-scope="scope"> | ||
48 | + <div | ||
49 | + class="label-box" | ||
50 | + :style="{ backgroundColor: scope.row.bgColor }" | ||
51 | + > | ||
52 | + {{ scope.row.label }} | ||
53 | + </div> | ||
54 | + </template> | ||
55 | + <template slot="operate" slot-scope="scope"> | ||
56 | + <el-button size="mini" type="primary" @click="handleEdit(scope.row)" | ||
57 | + >编辑</el-button | ||
58 | + > | ||
59 | + <el-button | ||
60 | + size="mini" | ||
61 | + type="danger" | ||
62 | + @click="handleDelete(scope.row)" | ||
63 | + >删除</el-button | ||
64 | + > | ||
65 | + </template> | ||
66 | + </zp-table-list> | ||
67 | + </div> | ||
68 | + <!-- list end --> | ||
69 | + </zp-page-list> | ||
70 | + <editDialog | ||
71 | + v-if="editShow" | ||
72 | + :info="labelInfo" | ||
73 | + @close="handleClose" | ||
74 | + ></editDialog> | ||
75 | + </div> | ||
76 | +</template> | ||
77 | +<script> | ||
78 | +import Clipboard from "clipboard"; | ||
79 | +import editDialog from "./components/editDialog"; | ||
80 | +import { apiGetLabelList, apiDelLabel } from "src/api/label"; | ||
81 | +export default { | ||
82 | + components: { | ||
83 | + editDialog, | ||
84 | + }, | ||
85 | + computed: { | ||
86 | + }, | ||
87 | + data() { | ||
88 | + return { | ||
89 | + listLoading: false, | ||
90 | + editShow: false, | ||
91 | + searchForm: { | ||
92 | + keyword: "", | ||
93 | + }, | ||
94 | + labelList: [], | ||
95 | + columns: [ | ||
96 | + { | ||
97 | + label: "_id", | ||
98 | + prop: "_id", | ||
99 | + slot: "_id", | ||
100 | + }, | ||
101 | + { | ||
102 | + label: "标签", | ||
103 | + prop: "label", | ||
104 | + slot: "label", | ||
105 | + }, | ||
106 | + { | ||
107 | + label: "背景色", | ||
108 | + prop: "bgColor", | ||
109 | + }, | ||
110 | + { | ||
111 | + label: "操作", | ||
112 | + slot: "operate", | ||
113 | + width: "150", | ||
114 | + }, | ||
115 | + ], | ||
116 | + }; | ||
117 | + }, | ||
118 | + mounted() { | ||
119 | + this.requestPageData = this.getLabelList; | ||
120 | + this.getLabelList(); | ||
121 | + }, | ||
122 | + | ||
123 | + methods: { | ||
124 | + getLabelList() { | ||
125 | + let params = { | ||
126 | + keyword: this.searchForm.keyword, | ||
127 | + pageindex: this.pageInfo.pageNum, | ||
128 | + pagesize: this.pageInfo.pageSize, | ||
129 | + }; | ||
130 | + this.listLoading = true; | ||
131 | + return apiGetLabelList(params) | ||
132 | + .then((res) => { | ||
133 | + let { list, total } = res.data; | ||
134 | + this.labelList = list; | ||
135 | + this.pageInfo.total = total; | ||
136 | + }) | ||
137 | + .catch((err) => { | ||
138 | + this.listLoading = false; | ||
139 | + console.log(err); | ||
140 | + }) | ||
141 | + .finally(() => { | ||
142 | + this.listLoading = false; | ||
143 | + }); | ||
144 | + }, | ||
145 | + handleCopyId() { | ||
146 | + let clipboard = new Clipboard(".clipboardBtn"); | ||
147 | + clipboard.on("success", (e) => { | ||
148 | + this.$message.success("复制成功"); | ||
149 | + clipboard.destroy(); | ||
150 | + }); | ||
151 | + clipboard.on("error", (e) => { | ||
152 | + this.$message.error("该浏览器不支持自动复制"); | ||
153 | + clipboard.destroy(); | ||
154 | + }); | ||
155 | + }, | ||
156 | + handleClose() { | ||
157 | + this.editShow = false; | ||
158 | + }, | ||
159 | + handleDelete(row) { | ||
160 | + this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", { | ||
161 | + confirmButtonText: "确定", | ||
162 | + cancelButtonText: "取消", | ||
163 | + type: "warning", | ||
164 | + }) | ||
165 | + .then(async () => { | ||
166 | + try { | ||
167 | + await this.handleDeleteLabel(row._id);; | ||
168 | + } catch (e) { | ||
169 | + this.$message.error("删除失败"); | ||
170 | + console.log(e); | ||
171 | + } | ||
172 | + }) | ||
173 | + .catch(() => { | ||
174 | + this.$message.info("已取消删除"); | ||
175 | + }); | ||
176 | + }, | ||
177 | + handleDeleteLabel(id) { | ||
178 | + return apiDelLabel({ id }) | ||
179 | + .then((res) => { | ||
180 | + this.$message.success("删除成功"); | ||
181 | + this.getLabelList(true); | ||
182 | + }) | ||
183 | + .catch((err) => { | ||
184 | + console.log(err); | ||
185 | + this.$message.info("删除失败"); | ||
186 | + }) | ||
187 | + .finally(() => { }); | ||
188 | + }, | ||
189 | + handleEdit(row) { | ||
190 | + this.editShow = true; | ||
191 | + this.labelInfo = row; | ||
192 | + }, | ||
193 | + }, | ||
194 | +}; | ||
195 | +</script> | ||
196 | + | ||
197 | +<style lang="less" scoped> | ||
198 | +.label-box { | ||
199 | + color: #fff; | ||
200 | + padding: 4px 0; | ||
201 | + border-radius: 12px; | ||
202 | + font-size: 14px; | ||
203 | + text-align: center; | ||
204 | + max-width: 150px; | ||
205 | +} | ||
206 | +</style> |
1 | +<template> | ||
2 | + <div class="sign_out"> | ||
3 | + <el-dropdown> | ||
4 | + <div class="el-dropdown-link"> | ||
5 | + <Icon name="avatar" class="avatar"></Icon> | ||
6 | + </div> | ||
7 | + <el-dropdown-menu slot="dropdown"> | ||
8 | + <el-dropdown-item>{{ userName }}</el-dropdown-item> | ||
9 | + <el-dropdown-item @click.native="removeCookie">退出</el-dropdown-item> | ||
10 | + </el-dropdown-menu> | ||
11 | + </el-dropdown> | ||
12 | + </div> | ||
13 | +</template> | ||
14 | +<script> | ||
15 | +import { removeToken } from "../../utils/auth"; | ||
16 | +import { mapGetters } from "vuex"; | ||
17 | +export default { | ||
18 | + methods: { | ||
19 | + removeCookie() { | ||
20 | + removeToken(); | ||
21 | + this.$store.dispatch("clearInfo"); | ||
22 | + this.$router.push("/login"); | ||
23 | + }, | ||
24 | + }, | ||
25 | + computed: { | ||
26 | + ...mapGetters(["userName"]), | ||
27 | + }, | ||
28 | +}; | ||
29 | +</script> | ||
30 | +<style lang="less" scoped> | ||
31 | +.sign_out { | ||
32 | + float: right; | ||
33 | + margin-right: 20px; | ||
34 | + > * { | ||
35 | + display: inline-block; | ||
36 | + vertical-align: middle; | ||
37 | + } | ||
38 | + .avatar { | ||
39 | + font-size: 40px; | ||
40 | + margin-top: 5px; | ||
41 | + color: @highlightColor; | ||
42 | + } | ||
43 | + img { | ||
44 | + width: 40px; | ||
45 | + height: 40px; | ||
46 | + border-radius: 10px; | ||
47 | + margin-top: 5px; | ||
48 | + } | ||
49 | + .sign_out_icon { | ||
50 | + color: #5a5e66; | ||
51 | + font-size: 20px; | ||
52 | + cursor: pointer; | ||
53 | + &:hover { | ||
54 | + color: #aaa; | ||
55 | + } | ||
56 | + } | ||
57 | +} | ||
58 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/admin/src/views/Layout/index.js
0 → 100644
1 | +<template> | ||
2 | + <div class="app-wrapper" :class="{hideSidebar: $store.state.app.slideBar.opened}"> | ||
3 | + <SlideBar class="slidebar-container"></SlideBar> | ||
4 | + <div class="main-container"> | ||
5 | + <NavBar></NavBar> | ||
6 | + <ContentView></ContentView> | ||
7 | + </div> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | + | ||
11 | +<script> | ||
12 | +import { SlideBar, NavBar, ContentView } from './index' | ||
13 | +export default { | ||
14 | + name: 'layout', | ||
15 | + components: { | ||
16 | + SlideBar, | ||
17 | + NavBar, | ||
18 | + ContentView | ||
19 | + } | ||
20 | + | ||
21 | +} | ||
22 | +</script> | ||
23 | + | ||
24 | + | ||
25 | +<style lang="less" scoped> | ||
26 | + .app-wrapper { | ||
27 | + &.hideSidebar { | ||
28 | + .slidebar-container{ | ||
29 | + width: 64px; | ||
30 | + overflow: inherit; | ||
31 | + } | ||
32 | + .main-container { | ||
33 | + margin-left: 64px; | ||
34 | + } | ||
35 | + } | ||
36 | + .slidebar-container { | ||
37 | + height: 100%; | ||
38 | + position: fixed; | ||
39 | + top: 0; | ||
40 | + bottom: 0; | ||
41 | + left: 0; | ||
42 | + z-index: 1001; | ||
43 | + overflow-y: auto; | ||
44 | + transition: width 0.28s ease-out; | ||
45 | + } | ||
46 | + .main-container { | ||
47 | + min-height: 100%; | ||
48 | + margin-left: 200px; | ||
49 | + transition: margin-left 0.28s ease-out; | ||
50 | + } | ||
51 | + } | ||
52 | +</style> | ||
53 | + | ||
54 | + |
1 | +<template> | ||
2 | + <el-breadcrumb class="levelbar-wrapper" separator="/"> | ||
3 | + <el-breadcrumb-item v-for="(item, index) in levelList" :key="index"> | ||
4 | + <router-link :to="item.path">{{ item.name }}</router-link> | ||
5 | + </el-breadcrumb-item> | ||
6 | + </el-breadcrumb> | ||
7 | +</template> | ||
8 | + | ||
9 | +<script> | ||
10 | +export default { | ||
11 | + created() { | ||
12 | + this.getBreadcrumb(); | ||
13 | + }, | ||
14 | + data() { | ||
15 | + return { | ||
16 | + levelList: [] | ||
17 | + }; | ||
18 | + }, | ||
19 | + methods: { | ||
20 | + getBreadcrumb() { | ||
21 | + let matched = this.$route.matched.filter(item => item.name); | ||
22 | + let first = matched[0], | ||
23 | + second = matched[1]; | ||
24 | + if (first && first.name !== '首页' && first.name !== '') { | ||
25 | + matched = [{ name: '首页', path: '/' }].concat(matched); | ||
26 | + } | ||
27 | + if (second && second.name === '首页') { | ||
28 | + this.levelList = [second]; | ||
29 | + } else { | ||
30 | + this.levelList = matched; | ||
31 | + } | ||
32 | + | ||
33 | + } | ||
34 | + }, | ||
35 | + watch: { | ||
36 | + $route() { | ||
37 | + this.getBreadcrumb(); | ||
38 | + } | ||
39 | + } | ||
40 | +}; | ||
41 | +</script> | ||
42 | + | ||
43 | +<style lang="less" scoped> | ||
44 | +.levelbar-wrapper.el-breadcrumb { | ||
45 | + display: inline-block; | ||
46 | + font-size: 14px; | ||
47 | + line-height: 50px; | ||
48 | + margin-left: 10px; | ||
49 | + .no-redirect { | ||
50 | + color: #97a8be; | ||
51 | + cursor: text; | ||
52 | + } | ||
53 | +} | ||
54 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<template> | ||
2 | + <div class="nav-bar-wrapper" separator="/"> | ||
3 | + <Hamburger | ||
4 | + class="hamburger" | ||
5 | + :toggleClick="toggleSlideBar" | ||
6 | + :isActive="app.slideBar.opened" | ||
7 | + ></Hamburger> | ||
8 | + <Levelbar class="levelbar"></Levelbar> | ||
9 | + <TabsView class="tabsview"></TabsView> | ||
10 | + <Account></Account> | ||
11 | + </div> | ||
12 | +</template> | ||
13 | + | ||
14 | +<script> | ||
15 | +import { mapState } from "vuex"; | ||
16 | +import Hamburger from "components/hamburger"; | ||
17 | +import Levelbar from "./levelbar"; | ||
18 | +import TabsView from "./tabsView"; | ||
19 | +import Account from "./account"; | ||
20 | + | ||
21 | +export default { | ||
22 | + components: { | ||
23 | + Hamburger, | ||
24 | + Levelbar, | ||
25 | + TabsView, | ||
26 | + Account, | ||
27 | + }, | ||
28 | + methods: { | ||
29 | + toggleSlideBar() { | ||
30 | + this.$store.dispatch("toggleSideBar"); | ||
31 | + }, | ||
32 | + }, | ||
33 | + computed: { | ||
34 | + ...mapState(["app"]), | ||
35 | + }, | ||
36 | +}; | ||
37 | +</script> | ||
38 | + | ||
39 | +<style lang="less" scoped> | ||
40 | +.nav-bar-wrapper { | ||
41 | + height: 50px; | ||
42 | + // line-height: 50px; | ||
43 | + background: #eef1f6; | ||
44 | + > * { | ||
45 | + display: inline-block; | ||
46 | + vertical-align: middle; | ||
47 | + } | ||
48 | + .hamburger { | ||
49 | + line-height: 58px; | ||
50 | + width: 40px; | ||
51 | + height: 50px; | ||
52 | + padding: 0 10px; | ||
53 | + } | ||
54 | + .levelbar { | ||
55 | + font-size: 14px; | ||
56 | + line-height: 50px; | ||
57 | + margin-left: 10px; | ||
58 | + } | ||
59 | + .tabsview { | ||
60 | + margin-left: 10px; | ||
61 | + } | ||
62 | +} | ||
63 | +</style> | ||
64 | + | ||
65 | + |
1 | +<template> | ||
2 | + <div class="silde-bar-wrapper"> | ||
3 | + <el-menu | ||
4 | + class="el-menu-vertical" | ||
5 | + :default-active="$route.path" | ||
6 | + unique-opened | ||
7 | + router | ||
8 | + :collapse="$store.state.app.slideBar.opened" | ||
9 | + > | ||
10 | + <template v-for="item in $store.state.permission.routes"> | ||
11 | + <el-menu-item | ||
12 | + v-if="!item.hidden && !item.dropdown" | ||
13 | + :index=" | ||
14 | + (item.path === '/' ? item.path : item.path + '/') + | ||
15 | + item.children[0].path | ||
16 | + " | ||
17 | + :key="item.path" | ||
18 | + > | ||
19 | + <Icon :name="item.icon" class="slide-icon"></Icon> | ||
20 | + <span slot="title">{{ item.name }}</span> | ||
21 | + </el-menu-item> | ||
22 | + <el-submenu | ||
23 | + v-if="!item.hidden && item.dropdown" | ||
24 | + :index="item.path" | ||
25 | + :key="item.path" | ||
26 | + > | ||
27 | + <template slot="title"> | ||
28 | + <Icon :name="item.icon" class="slide-icon"></Icon> | ||
29 | + <span>{{ item.name }}</span> | ||
30 | + </template> | ||
31 | + <template v-for="child in getSubMenuHiddleList(item.children)"> | ||
32 | + <el-menu-item | ||
33 | + :index="item.path + '/' + child.path" | ||
34 | + :key="child.path" | ||
35 | + >{{ child.name }}</el-menu-item | ||
36 | + > | ||
37 | + </template> | ||
38 | + </el-submenu> | ||
39 | + </template> | ||
40 | + </el-menu> | ||
41 | + </div> | ||
42 | +</template> | ||
43 | + | ||
44 | +<script> | ||
45 | +export default { | ||
46 | + computed: { | ||
47 | + getSubMenuHiddleList() { | ||
48 | + return (list) => | ||
49 | + list.filter(item => !item.hidden) | ||
50 | + }, | ||
51 | + }, | ||
52 | + data() { | ||
53 | + return { | ||
54 | + } | ||
55 | + }, | ||
56 | + methods: { | ||
57 | + } | ||
58 | +}; | ||
59 | +</script> | ||
60 | + | ||
61 | +<style lang="less" scoped> | ||
62 | +.silde-bar-wrapper { | ||
63 | + .el-menu-vertical:not(.el-menu--collapse) { | ||
64 | + width: 200px; | ||
65 | + height: 100%; | ||
66 | + } | ||
67 | + .el-menu-vertical { | ||
68 | + height: 100%; | ||
69 | + } | ||
70 | + .el-menu { | ||
71 | + border-right: none; | ||
72 | + } | ||
73 | + .slide-icon { | ||
74 | + width: 24px; | ||
75 | + font-size: 20px; | ||
76 | + text-align: center; | ||
77 | + vertical-align: middle; | ||
78 | + } | ||
79 | +} | ||
80 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<template> | ||
2 | + <div class="tabs-wwrapper"> | ||
3 | + <router-link | ||
4 | + class="tab-view" | ||
5 | + :to="tag.path" | ||
6 | + v-for="tag in getTags" | ||
7 | + :key="tag.name" | ||
8 | + > | ||
9 | + <el-tag | ||
10 | + size="small" | ||
11 | + closable | ||
12 | + :type="getIsActive(tag) ? '' : 'info'" | ||
13 | + @close="closeTagView(tag, $event)" | ||
14 | + > | ||
15 | + {{ tag.name }} | ||
16 | + </el-tag> | ||
17 | + </router-link> | ||
18 | + </div> | ||
19 | +</template> | ||
20 | + | ||
21 | +<script> | ||
22 | +export default { | ||
23 | + computed: { | ||
24 | + getTags() { | ||
25 | + let tagArr = this.$store.state.app.tagViews; | ||
26 | + return tagArr.filter((item) => item.path !== "/article/edit").slice(-6); | ||
27 | + }, | ||
28 | + }, | ||
29 | + watch: { | ||
30 | + $route() { | ||
31 | + this.addTagView(); | ||
32 | + }, | ||
33 | + }, | ||
34 | + mounted() { | ||
35 | + this.getIsActive(); | ||
36 | + }, | ||
37 | + methods: { | ||
38 | + getIsActive(tag) { | ||
39 | + if (!tag) return; | ||
40 | + return tag.path === this.$route.path; | ||
41 | + }, | ||
42 | + closeTagView(tag, e) { | ||
43 | + this.$store.dispatch("delTagView", tag); | ||
44 | + e.preventDefault(); | ||
45 | + }, | ||
46 | + generateRoute() { | ||
47 | + if (this.$route.matched[this.$route.matched.length - 1].name) { | ||
48 | + return this.$route.matched[this.$route.matched.length - 1]; | ||
49 | + } | ||
50 | + return this.$route.matched[0]; | ||
51 | + }, | ||
52 | + addTagView() { | ||
53 | + this.$store.dispatch("addTagView", this.generateRoute()); | ||
54 | + }, | ||
55 | + }, | ||
56 | +}; | ||
57 | +</script> | ||
58 | + | ||
59 | + | ||
60 | +<style lang="less" scoped> | ||
61 | +.tab-view { | ||
62 | + margin-left: 10px; | ||
63 | +} | ||
64 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/admin/src/views/Login/index.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="login-wrapper"> | ||
3 | + <el-form class="login-form"> | ||
4 | + <h3>博客系统登录</h3> | ||
5 | + <el-form-item prop="username"> | ||
6 | + <Icon name="user" class="icon-user"></Icon> | ||
7 | + <el-input | ||
8 | + type="text" | ||
9 | + placeholder="请输入用户名" | ||
10 | + class="username" | ||
11 | + v-model="loginInfo.username" | ||
12 | + @keydown.enter.native="login" | ||
13 | + /> | ||
14 | + </el-form-item> | ||
15 | + <el-form-item prop="password"> | ||
16 | + <Icon name="password" class="icon-pwd"></Icon> | ||
17 | + <el-input | ||
18 | + type="password" | ||
19 | + placeholder="请输入密码" | ||
20 | + class="pwd" | ||
21 | + v-model="loginInfo.pwd" | ||
22 | + @keydown.enter.native="login" | ||
23 | + /> | ||
24 | + </el-form-item> | ||
25 | + <el-button type="primary" class="submit" @click="login" :loading="loading" | ||
26 | + >登录</el-button | ||
27 | + > | ||
28 | + </el-form> | ||
29 | + </div> | ||
30 | +</template> | ||
31 | + | ||
32 | +<script> | ||
33 | +export default { | ||
34 | + name: "login", | ||
35 | + data() { | ||
36 | + return { | ||
37 | + loading: false, | ||
38 | + msg: "", | ||
39 | + loginInfo: { | ||
40 | + username: "", | ||
41 | + pwd: "", | ||
42 | + }, | ||
43 | + }; | ||
44 | + }, | ||
45 | + methods: { | ||
46 | + async login() { | ||
47 | + this.loading = true; | ||
48 | + if (!this.loginInfo.username) { | ||
49 | + this.msg = "请输入用户名"; | ||
50 | + } else if (!this.loginInfo.pwd) { | ||
51 | + this.msg = "请输入密码"; | ||
52 | + } | ||
53 | + if (this.msg) { | ||
54 | + this.$message({ | ||
55 | + message: this.msg, | ||
56 | + type: "warning", | ||
57 | + }); | ||
58 | + this.msg = ""; | ||
59 | + this.loading = false; | ||
60 | + return; | ||
61 | + } | ||
62 | + try { | ||
63 | + await this.$store.dispatch("userLogin", this.loginInfo); | ||
64 | + this.$router.push("/article/list"); | ||
65 | + } catch (e) { | ||
66 | + console.log(e); | ||
67 | + } | ||
68 | + this.loading = false; | ||
69 | + }, | ||
70 | + }, | ||
71 | +}; | ||
72 | +</script> | ||
73 | + | ||
74 | +<style lang="less"> | ||
75 | +.login-wrapper { | ||
76 | + width: 100%; | ||
77 | + height: 100%; | ||
78 | + position: fixed; | ||
79 | + background: #2d3a4b; | ||
80 | + .login-form { | ||
81 | + width: 400px; | ||
82 | + padding: 35px; | ||
83 | + position: absolute; | ||
84 | + left: 0%; | ||
85 | + right: 0%; | ||
86 | + top: 20%; | ||
87 | + margin: auto; | ||
88 | + } | ||
89 | + .el-form-item { | ||
90 | + border: 1px solid rgba(255, 255, 255, 0.1); | ||
91 | + background: rgba(0, 0, 0, 0.1); | ||
92 | + border-radius: 5px; | ||
93 | + color: #454545; | ||
94 | + } | ||
95 | + h3 { | ||
96 | + font-size: 26px; | ||
97 | + color: #fff; | ||
98 | + margin-bottom: 50px; | ||
99 | + text-align: center; | ||
100 | + } | ||
101 | + .icon { | ||
102 | + color: #889aa4; | ||
103 | + vertical-align: middle; | ||
104 | + width: 1em; | ||
105 | + height: 1em; | ||
106 | + display: inline-block; | ||
107 | + margin-left: 10px; | ||
108 | + } | ||
109 | + .icon-user, | ||
110 | + .icon-pwd { | ||
111 | + width: 1.5em; | ||
112 | + height: 1.5em; | ||
113 | + margin-left: 8px; | ||
114 | + } | ||
115 | + input { | ||
116 | + background: transparent; | ||
117 | + border: 0px; | ||
118 | + -webkit-appearance: none; | ||
119 | + border-radius: 0px; | ||
120 | + padding: 12px 5px 12px 15px; | ||
121 | + color: #889aa4; | ||
122 | + height: 47px; | ||
123 | + vertical-align: middle; | ||
124 | + color: #eee; | ||
125 | + } | ||
126 | + .username input { | ||
127 | + padding: 12px 5px 12px 10px; | ||
128 | + } | ||
129 | + .el-input { | ||
130 | + display: inline-block; | ||
131 | + height: 47px; | ||
132 | + width: 85%; | ||
133 | + } | ||
134 | + .submit { | ||
135 | + width: 100%; | ||
136 | + } | ||
137 | +} | ||
138 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<template> | ||
2 | + <div class="message-list"> | ||
3 | + <zp-page-list> | ||
4 | + <template v-slot:header> | ||
5 | + <span>留言列表</span> | ||
6 | + </template> | ||
7 | + <!-- filter start --> | ||
8 | + <div slot="filter"> | ||
9 | + <el-form | ||
10 | + ref="searchForm" | ||
11 | + class="zp-search-form" | ||
12 | + :inline="true" | ||
13 | + :model="searchForm" | ||
14 | + > | ||
15 | + <el-form-item label="关键词:" prop="keyword"> | ||
16 | + <el-input | ||
17 | + placeholder="请输入内容、昵称" | ||
18 | + v-model="searchForm.keyword" | ||
19 | + @keydown.enter.native="getMessageList(true)" | ||
20 | + ></el-input> | ||
21 | + </el-form-item> | ||
22 | + <el-button type="primary" @click="getMessageList(true)"> | ||
23 | + 查询 | ||
24 | + </el-button> | ||
25 | + </el-form> | ||
26 | + </div> | ||
27 | + <!-- filter end --> | ||
28 | + <!-- list start --> | ||
29 | + <div slot="list"> | ||
30 | + <zp-table-list | ||
31 | + ref="sensitiveBehaviorRecordList" | ||
32 | + :loading="listLoading" | ||
33 | + :source="messageList" | ||
34 | + :count="pageInfo.total" | ||
35 | + :columns="columns" | ||
36 | + @size-change="handleSizeChange" | ||
37 | + @current-change="handleCurrentChange" | ||
38 | + > | ||
39 | + <template slot="_id" slot-scope="scope"> | ||
40 | + <el-button | ||
41 | + icon="el-icon-document-copy" | ||
42 | + type="primary" | ||
43 | + size="mini" | ||
44 | + class="clipboardBtn" | ||
45 | + :data-clipboard-text="scope.row._id" | ||
46 | + @click="handleCopyId" | ||
47 | + >复制 | ||
48 | + </el-button> | ||
49 | + </template> | ||
50 | + <template slot="content" slot-scope="scope"> | ||
51 | + <div v-html="scope.row.content"></div> | ||
52 | + </template> | ||
53 | + <template slot="createTime" slot-scope="scope"> | ||
54 | + {{ scope.row.createTime | formatTime("yyyy-MM-dd hh:mm:ss") }} | ||
55 | + </template> | ||
56 | + <template slot="operate" slot-scope="scope"> | ||
57 | + <el-button | ||
58 | + size="mini" | ||
59 | + type="danger" | ||
60 | + @click="handleDelete(scope.row)" | ||
61 | + >删除</el-button | ||
62 | + > | ||
63 | + </template> | ||
64 | + </zp-table-list> | ||
65 | + </div> | ||
66 | + <!-- list end --> | ||
67 | + </zp-page-list> | ||
68 | + </div> | ||
69 | +</template> | ||
70 | + | ||
71 | +<script> | ||
72 | +import Clipboard from "clipboard"; | ||
73 | +import { apiGetMessageList, apiDelMessage } from "src/api/message"; | ||
74 | +export default { | ||
75 | + name: "messageList", | ||
76 | + components: {}, | ||
77 | + computed: {}, | ||
78 | + data() { | ||
79 | + return { | ||
80 | + listLoading: false, | ||
81 | + searchForm: { | ||
82 | + keyword: "", | ||
83 | + }, | ||
84 | + messageList: [], | ||
85 | + columns: [ | ||
86 | + { | ||
87 | + label: "_id", | ||
88 | + prop: "_id", | ||
89 | + slot: "_id", | ||
90 | + }, | ||
91 | + { | ||
92 | + label: "昵称", | ||
93 | + prop: "nickname", | ||
94 | + }, | ||
95 | + { | ||
96 | + label: "头像颜色", | ||
97 | + prop: "headerColor", | ||
98 | + }, | ||
99 | + { | ||
100 | + label: "评论内容", | ||
101 | + prop: "content", | ||
102 | + slot: "content", | ||
103 | + showTooltip: true, | ||
104 | + }, | ||
105 | + { | ||
106 | + label: "时间", | ||
107 | + prop: "createTime", | ||
108 | + slot: "createTime", | ||
109 | + }, | ||
110 | + { | ||
111 | + label: "点赞数", | ||
112 | + prop: "likes", | ||
113 | + }, | ||
114 | + { | ||
115 | + label: "操作", | ||
116 | + slot: "operate", | ||
117 | + width: "150", | ||
118 | + }, | ||
119 | + ], | ||
120 | + }; | ||
121 | + }, | ||
122 | + created() { | ||
123 | + this.requestPageData = this.getMessageList; | ||
124 | + this.getMessageList(); | ||
125 | + }, | ||
126 | + mounted() { }, | ||
127 | + methods: { | ||
128 | + getMessageList() { | ||
129 | + let params = { | ||
130 | + keyword: this.searchForm.keyword, | ||
131 | + pageindex: this.pageInfo.pageNum, | ||
132 | + pagesize: this.pageInfo.pageSize, | ||
133 | + }; | ||
134 | + this.listLoading = true; | ||
135 | + return apiGetMessageList(params) | ||
136 | + .then((res) => { | ||
137 | + let { list, total } = res.data; | ||
138 | + this.messageList = list; | ||
139 | + this.pageInfo.total = total; | ||
140 | + }) | ||
141 | + .catch((err) => { | ||
142 | + this.listLoading = false; | ||
143 | + console.log(err); | ||
144 | + }) | ||
145 | + .finally(() => { | ||
146 | + this.listLoading = false; | ||
147 | + this.hasLoad = true; | ||
148 | + }); | ||
149 | + }, | ||
150 | + handleCopyId() { | ||
151 | + let clipboard = new Clipboard(".clipboardBtn"); | ||
152 | + clipboard.on("success", (e) => { | ||
153 | + this.$message.success("复制成功"); | ||
154 | + clipboard.destroy(); | ||
155 | + }); | ||
156 | + clipboard.on("error", (e) => { | ||
157 | + this.$message.error("该浏览器不支持自动复制"); | ||
158 | + clipboard.destroy(); | ||
159 | + }); | ||
160 | + }, | ||
161 | + handleDeleteMessage(id) { | ||
162 | + return apiDelMessage({ id }) | ||
163 | + .then((res) => { | ||
164 | + this.$message.success("删除成功"); | ||
165 | + this.getMessageList(true); | ||
166 | + }) | ||
167 | + .catch((err) => { | ||
168 | + console.log(err); | ||
169 | + this.$message.info("删除失败"); | ||
170 | + }) | ||
171 | + .finally(() => { }); | ||
172 | + }, | ||
173 | + handleDelete(row) { | ||
174 | + this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", { | ||
175 | + confirmButtonText: "确定", | ||
176 | + cancelButtonText: "取消", | ||
177 | + type: "warning", | ||
178 | + }) | ||
179 | + .then(() => { | ||
180 | + this.handleDeleteMessage(row._id); | ||
181 | + }) | ||
182 | + .catch(() => { | ||
183 | + this.$message.info("已取消删除"); | ||
184 | + }); | ||
185 | + }, | ||
186 | + }, | ||
187 | +}; | ||
188 | +</script> | ||
189 | + | ||
190 | + | ||
191 | +<style lang="less" scoped> | ||
192 | +</style> |
1 | +<template> | ||
2 | + <div class="reply-list"> | ||
3 | + <zp-page-list> | ||
4 | + <template v-slot:header> | ||
5 | + <span>回复列表</span> | ||
6 | + </template> | ||
7 | + | ||
8 | + <!-- list start --> | ||
9 | + <div slot="list"> | ||
10 | + <zp-table-list | ||
11 | + ref="sensitiveBehaviorRecordList" | ||
12 | + :loading="listLoading" | ||
13 | + :source="replyList" | ||
14 | + :count="pageInfo.total" | ||
15 | + :columns="columns" | ||
16 | + @size-change="handleSizeChange" | ||
17 | + @current-change="handleCurrentChange" | ||
18 | + > | ||
19 | + <template slot="_id" slot-scope="scope"> | ||
20 | + <el-button | ||
21 | + icon="el-icon-document-copy" | ||
22 | + type="primary" | ||
23 | + size="mini" | ||
24 | + class="clipboardBtn" | ||
25 | + :data-clipboard-text="scope.row._id" | ||
26 | + @click="handleCopyId" | ||
27 | + >复制 | ||
28 | + </el-button> | ||
29 | + </template> | ||
30 | + <template slot="replyContent" slot-scope="scope"> | ||
31 | + <div v-html="scope.row.replyContent"></div> | ||
32 | + </template> | ||
33 | + <template slot="replyTime" slot-scope="scope"> | ||
34 | + {{ scope.row.replyTime | formatTime("yyyy-MM-dd hh:mm:ss") }} | ||
35 | + </template> | ||
36 | + <template slot="operate" slot-scope="scope"> | ||
37 | + <el-button | ||
38 | + size="mini" | ||
39 | + type="danger" | ||
40 | + @click="handleDelete(scope.row)" | ||
41 | + >删除</el-button | ||
42 | + > | ||
43 | + </template> | ||
44 | + </zp-table-list> | ||
45 | + </div> | ||
46 | + <!-- list end --> | ||
47 | + </zp-page-list> | ||
48 | + </div> | ||
49 | +</template> | ||
50 | + | ||
51 | +<script> | ||
52 | +import Clipboard from "clipboard"; | ||
53 | +import { apiGetMessageList, apiDelReply } from "src/api/message"; | ||
54 | +export default { | ||
55 | + name: "replyList", | ||
56 | + components: {}, | ||
57 | + computed: {}, | ||
58 | + data() { | ||
59 | + return { | ||
60 | + listLoading: false, | ||
61 | + searchForm: { | ||
62 | + keyword: "", | ||
63 | + }, | ||
64 | + messageList: [], | ||
65 | + replyList: [], | ||
66 | + columns: [ | ||
67 | + { | ||
68 | + label: "留言_id", | ||
69 | + prop: "_id", | ||
70 | + slot: "_id", | ||
71 | + }, | ||
72 | + { | ||
73 | + label: "回复用户", | ||
74 | + prop: "replyUser", | ||
75 | + }, | ||
76 | + { | ||
77 | + label: "被回复用户", | ||
78 | + prop: "byReplyUser", | ||
79 | + }, | ||
80 | + { | ||
81 | + label: "头像颜色", | ||
82 | + prop: "replyHeaderColor", | ||
83 | + }, | ||
84 | + { | ||
85 | + label: "回复内容", | ||
86 | + prop: "replyContent", | ||
87 | + slot: "replyContent", | ||
88 | + showTooltip: true, | ||
89 | + }, | ||
90 | + { | ||
91 | + label: "回复时间", | ||
92 | + prop: "replyTime", | ||
93 | + slot: "replyTime", | ||
94 | + }, | ||
95 | + { | ||
96 | + label: "操作", | ||
97 | + slot: "operate", | ||
98 | + width: "150", | ||
99 | + }, | ||
100 | + ], | ||
101 | + }; | ||
102 | + }, | ||
103 | + created() { | ||
104 | + this.requestPageData = this.getMessageList; | ||
105 | + this.getMessageList(); | ||
106 | + }, | ||
107 | + mounted() { }, | ||
108 | + methods: { | ||
109 | + getMessageList() { | ||
110 | + let params = { | ||
111 | + keyword: this.searchForm.keyword, | ||
112 | + pageindex: this.pageInfo.pageNum, | ||
113 | + pagesize: this.pageInfo.pageSize, | ||
114 | + }; | ||
115 | + this.listLoading = true; | ||
116 | + return apiGetMessageList(params) | ||
117 | + .then((res) => { | ||
118 | + let { list } = res.data; | ||
119 | + this.messageList = list; | ||
120 | + this.replyList = this.messageList | ||
121 | + .map((item) => item.replyList) | ||
122 | + .flat(); | ||
123 | + this.pageInfo.total = this.replyList.length; | ||
124 | + }) | ||
125 | + .catch((err) => { | ||
126 | + this.listLoading = false; | ||
127 | + console.log(err); | ||
128 | + }) | ||
129 | + .finally(() => { | ||
130 | + this.listLoading = false; | ||
131 | + this.hasLoad = true; | ||
132 | + }); | ||
133 | + }, | ||
134 | + handleCopyId() { | ||
135 | + let clipboard = new Clipboard(".clipboardBtn"); | ||
136 | + clipboard.on("success", (e) => { | ||
137 | + this.$message.success("复制成功"); | ||
138 | + clipboard.destroy(); | ||
139 | + }); | ||
140 | + clipboard.on("error", (e) => { | ||
141 | + this.$message.error("该浏览器不支持自动复制"); | ||
142 | + clipboard.destroy(); | ||
143 | + }); | ||
144 | + }, | ||
145 | + handleDeleteReply(_id) { | ||
146 | + return apiDelReply({ _id }) | ||
147 | + .then((res) => { | ||
148 | + this.$message.success("删除成功"); | ||
149 | + this.getMessageList(true); | ||
150 | + }) | ||
151 | + .catch((err) => { | ||
152 | + console.log(err); | ||
153 | + this.$message.info("删除失败"); | ||
154 | + }) | ||
155 | + .finally(() => { }); | ||
156 | + }, | ||
157 | + handleDelete(row) { | ||
158 | + this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", { | ||
159 | + confirmButtonText: "确定", | ||
160 | + cancelButtonText: "取消", | ||
161 | + type: "warning", | ||
162 | + }) | ||
163 | + .then(() => { | ||
164 | + this.handleDeleteReply(row._id); | ||
165 | + }) | ||
166 | + .catch(() => { | ||
167 | + this.$message.info("已取消删除"); | ||
168 | + }); | ||
169 | + }, | ||
170 | + }, | ||
171 | +}; | ||
172 | +</script> | ||
173 | + | ||
174 | + | ||
175 | +<style lang="less" scoped> | ||
176 | +</style> |
1 | +<template> | ||
2 | + <div class="permission-add"> | ||
3 | + <zp-page-edit :back="true" @back="$router.back()"> | ||
4 | + <div slot="header"> | ||
5 | + {{ header }} | ||
6 | + </div> | ||
7 | + <el-form | ||
8 | + :model="info" | ||
9 | + :rules="rules" | ||
10 | + ref="form" | ||
11 | + label-width="100px" | ||
12 | + class="form" | ||
13 | + > | ||
14 | + <el-form-item label="用户名:" prop="username"> | ||
15 | + <el-input type="text" v-model="info.username"></el-input> | ||
16 | + </el-form-item> | ||
17 | + <el-form-item label="密码:" prop="pwd"> | ||
18 | + <el-input type="password" v-model="info.pwd"></el-input> | ||
19 | + </el-form-item> | ||
20 | + <el-form-item label="权限:" prop="roles"> | ||
21 | + <el-select | ||
22 | + v-model="info.roles" | ||
23 | + multiple | ||
24 | + placeholder="请选择" | ||
25 | + class="block" | ||
26 | + > | ||
27 | + <el-option | ||
28 | + v-for="item in roles" | ||
29 | + :key="item.value" | ||
30 | + :label="item.label" | ||
31 | + :value="item.value" | ||
32 | + > | ||
33 | + </el-option> | ||
34 | + </el-select> | ||
35 | + </el-form-item> | ||
36 | + <el-form-item> | ||
37 | + <el-button | ||
38 | + type="primary" | ||
39 | + @click="handleSubmit('form')" | ||
40 | + :loading="loading" | ||
41 | + >立即创建</el-button | ||
42 | + > | ||
43 | + </el-form-item> | ||
44 | + </el-form> | ||
45 | + </zp-page-edit> | ||
46 | + </div> | ||
47 | +</template> | ||
48 | + | ||
49 | +<script> | ||
50 | +export default { | ||
51 | + name: "permissionAdd", | ||
52 | + components: {}, | ||
53 | + data() { | ||
54 | + return { | ||
55 | + header: "添加管理员", | ||
56 | + info: { | ||
57 | + username: "", | ||
58 | + pwd: "", | ||
59 | + avatar: "", | ||
60 | + roles: ["default"], | ||
61 | + }, | ||
62 | + roles: [ | ||
63 | + { label: "超级管理员", value: "admin" }, | ||
64 | + { label: "普通管理员", value: "default" }, | ||
65 | + ], | ||
66 | + loading: false, | ||
67 | + rules: { | ||
68 | + username: [ | ||
69 | + { required: true, message: "请填写用户名", trigger: "blur" }, | ||
70 | + ], | ||
71 | + pwd: [{ required: true, message: "请填写密码", trigger: "blur" }], | ||
72 | + roles: [ | ||
73 | + { | ||
74 | + required: true, | ||
75 | + message: "请选择权限", | ||
76 | + trigger: "change", | ||
77 | + type: "array", | ||
78 | + }, | ||
79 | + ], | ||
80 | + }, | ||
81 | + }; | ||
82 | + }, | ||
83 | + methods: { | ||
84 | + handleSubmit(formName) { | ||
85 | + this.loading = true; | ||
86 | + this.$refs[formName].validate(async (valid) => { | ||
87 | + if (valid) { | ||
88 | + try { | ||
89 | + await this.$store.dispatch("addUser", this.info); | ||
90 | + this.loading = false; | ||
91 | + this.$router.push("/permission/list"); | ||
92 | + this.$message.success("新增成功"); | ||
93 | + } catch (e) { | ||
94 | + this.$message.error("新增失败"); | ||
95 | + this.loading = false; | ||
96 | + } | ||
97 | + } else { | ||
98 | + console.log("error submit!!"); | ||
99 | + this.loading = false; | ||
100 | + return false; | ||
101 | + } | ||
102 | + }); | ||
103 | + }, | ||
104 | + }, | ||
105 | +}; | ||
106 | +</script> | ||
107 | + | ||
108 | + | ||
109 | +<style lang="less" scoped> | ||
110 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<template> | ||
2 | + <zp-dialog | ||
3 | + title="管理员编辑" | ||
4 | + :visible.sync="dialogTableVisible" | ||
5 | + @close="close" | ||
6 | + width="480px" | ||
7 | + :destroy-on-close="true" | ||
8 | + v-bind="$attrs" | ||
9 | + v-on="$listeners" | ||
10 | + > | ||
11 | + <el-form :model="info" :rules="rules" ref="form"> | ||
12 | + <el-form-item label="用户名:" prop="username"> | ||
13 | + <el-input type="text" v-model="info.username"></el-input> | ||
14 | + </el-form-item> | ||
15 | + <el-form-item label="原密码:" prop="old_pwd"> | ||
16 | + <el-input type="password" v-model="info.old_pwd"></el-input> | ||
17 | + </el-form-item> | ||
18 | + <el-form-item label="新密码:" prop="pwd"> | ||
19 | + <el-input type="password" v-model="info.pwd"></el-input> | ||
20 | + </el-form-item> | ||
21 | + <el-form-item label="权限:" prop="roles"> | ||
22 | + <el-select | ||
23 | + v-model="info.roles" | ||
24 | + multiple | ||
25 | + placeholder="请选择" | ||
26 | + class="block" | ||
27 | + > | ||
28 | + <el-option | ||
29 | + v-for="item in roles" | ||
30 | + :key="item.value" | ||
31 | + :label="item.label" | ||
32 | + :value="item.value" | ||
33 | + > | ||
34 | + </el-option> | ||
35 | + </el-select> | ||
36 | + </el-form-item> | ||
37 | + </el-form> | ||
38 | + <div slot="footer" class="dialog-footer"> | ||
39 | + <el-button @click="close">取消</el-button> | ||
40 | + <el-button | ||
41 | + type="primary" | ||
42 | + :loading="loading" | ||
43 | + @click="handleSubmit('form')" | ||
44 | + > | ||
45 | + 确定 | ||
46 | + </el-button> | ||
47 | + </div> | ||
48 | + </zp-dialog> | ||
49 | +</template> | ||
50 | + | ||
51 | +<script> | ||
52 | +export default { | ||
53 | + props: ["info"], | ||
54 | + components: {}, | ||
55 | + data() { | ||
56 | + return { | ||
57 | + roles: [ | ||
58 | + { label: "超级管理员", value: "admin" }, | ||
59 | + { label: "普通管理员", value: "default" }, | ||
60 | + ], | ||
61 | + loading: false, | ||
62 | + dialogTableVisible: true, | ||
63 | + rules: { | ||
64 | + username: [ | ||
65 | + { required: true, message: "请填写用户名", trigger: "blur" }, | ||
66 | + ], | ||
67 | + old_pwd: [{ required: true, message: "请填写原密码", trigger: "blur" }], | ||
68 | + pwd: [{ required: true, message: "请填写密码", trigger: "blur" }], | ||
69 | + roles: [ | ||
70 | + { | ||
71 | + required: true, | ||
72 | + message: "请选择权限", | ||
73 | + trigger: "change", | ||
74 | + type: "array", | ||
75 | + }, | ||
76 | + ], | ||
77 | + }, | ||
78 | + }; | ||
79 | + }, | ||
80 | + methods: { | ||
81 | + close() { | ||
82 | + this.$emit("close"); | ||
83 | + }, | ||
84 | + handleSubmit(formName) { | ||
85 | + this.loading = true; | ||
86 | + this.$refs[formName].validate(async (valid) => { | ||
87 | + if (valid) { | ||
88 | + try { | ||
89 | + delete this.info.createTime; | ||
90 | + delete this.info.releaseTime; | ||
91 | + await this.$store.dispatch("updateUser", this.info); | ||
92 | + this.loading = false; | ||
93 | + this.$message.success("编辑成功"); | ||
94 | + this.close(); | ||
95 | + } catch (e) { | ||
96 | + this.info.pwd = ""; | ||
97 | + this.info.old_pwd = ""; | ||
98 | + this.loading = false; | ||
99 | + this.$message.error("编辑失败"); | ||
100 | + } | ||
101 | + } else { | ||
102 | + console.log("error submit!!"); | ||
103 | + this.loading = false; | ||
104 | + return false; | ||
105 | + } | ||
106 | + }); | ||
107 | + }, | ||
108 | + }, | ||
109 | +}; | ||
110 | +</script> | ||
111 | + | ||
112 | + | ||
113 | +<style lang="less" scoped> | ||
114 | +.el-form { | ||
115 | + .el-form-item { | ||
116 | + display: flex; | ||
117 | + align-items: center; | ||
118 | + /deep/ .el-form-item__label { | ||
119 | + min-width: 80px; | ||
120 | + } | ||
121 | + /deep/.el-form-item__content { | ||
122 | + margin-left: 0 !important; | ||
123 | + width: 260px; | ||
124 | + .el-select { | ||
125 | + width: 260px; | ||
126 | + } | ||
127 | + } | ||
128 | + } | ||
129 | +} | ||
130 | +</style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<template> | ||
2 | + <div class="article-list"> | ||
3 | + <zp-page-list> | ||
4 | + <template v-slot:header> | ||
5 | + <span>管理员列表</span> | ||
6 | + </template> | ||
7 | + <!-- filter start --> | ||
8 | + <div slot="filter"> | ||
9 | + <el-form | ||
10 | + ref="searchForm" | ||
11 | + class="zp-search-form" | ||
12 | + :inline="true" | ||
13 | + :model="searchForm" | ||
14 | + > | ||
15 | + <el-form-item label="关键词:" prop="keyword"> | ||
16 | + <el-input | ||
17 | + placeholder="请输入用户名" | ||
18 | + v-model="searchForm.keyword" | ||
19 | + @keydown.enter.native="getUserList" | ||
20 | + ></el-input> | ||
21 | + </el-form-item> | ||
22 | + <el-button type="primary" @click="getUserList"> 查询 </el-button> | ||
23 | + </el-form> | ||
24 | + </div> | ||
25 | + <!-- filter end --> | ||
26 | + <!-- list start --> | ||
27 | + <div slot="list"> | ||
28 | + <zp-table-list | ||
29 | + :loading="listLoading" | ||
30 | + :source="userList" | ||
31 | + :count="userTotal" | ||
32 | + :columns="columns" | ||
33 | + @size-change="handleSizeChange" | ||
34 | + @current-change="handleCurrentChange" | ||
35 | + > | ||
36 | + <template slot="_id" slot-scope="scope"> | ||
37 | + <el-button | ||
38 | + icon="el-icon-document-copy" | ||
39 | + type="primary" | ||
40 | + size="mini" | ||
41 | + class="clipboardBtn" | ||
42 | + :data-clipboard-text="scope.row._id" | ||
43 | + @click="handleCopyId" | ||
44 | + >复制 | ||
45 | + </el-button> | ||
46 | + </template> | ||
47 | + <template slot="roles" slot-scope="scope"> | ||
48 | + <el-tag | ||
49 | + class="tag" | ||
50 | + type="primary" | ||
51 | + close-transition | ||
52 | + v-for="(tag, index) in scope.row.roles" | ||
53 | + :key="index" | ||
54 | + >{{ tag }}</el-tag | ||
55 | + > | ||
56 | + </template> | ||
57 | + <template slot="operate" slot-scope="scope"> | ||
58 | + <el-button size="mini" type="primary" @click="handleEdit(scope.row)" | ||
59 | + >编辑</el-button | ||
60 | + > | ||
61 | + <el-button | ||
62 | + size="mini" | ||
63 | + type="danger" | ||
64 | + @click="handleDelete(scope.row)" | ||
65 | + >删除</el-button | ||
66 | + > | ||
67 | + </template> | ||
68 | + </zp-table-list> | ||
69 | + </div> | ||
70 | + <!-- list end --> | ||
71 | + </zp-page-list> | ||
72 | + <editDialog | ||
73 | + v-if="editShow" | ||
74 | + :info="userInfo" | ||
75 | + @close="handleClose" | ||
76 | + ></editDialog> | ||
77 | + </div> | ||
78 | +</template> | ||
79 | +<script> | ||
80 | +import Clipboard from "clipboard"; | ||
81 | +import editDialog from "./components/editDialog"; | ||
82 | +import { mapGetters } from "vuex"; | ||
83 | +export default { | ||
84 | + components: { | ||
85 | + editDialog, | ||
86 | + }, | ||
87 | + computed: { | ||
88 | + ...mapGetters(["userList", "userTotal"]), | ||
89 | + }, | ||
90 | + data() { | ||
91 | + return { | ||
92 | + listLoading: false, | ||
93 | + editShow: false, | ||
94 | + searchForm: { | ||
95 | + keyword: "", | ||
96 | + }, | ||
97 | + userInfo: {}, | ||
98 | + columns: [ | ||
99 | + { | ||
100 | + label: "_id", | ||
101 | + prop: "_id", | ||
102 | + slot: "_id", | ||
103 | + }, | ||
104 | + { | ||
105 | + label: "用户名", | ||
106 | + prop: "username", | ||
107 | + }, | ||
108 | + { | ||
109 | + label: "权限", | ||
110 | + prop: "roles", | ||
111 | + slot: "roles", | ||
112 | + }, | ||
113 | + { | ||
114 | + label: "操作", | ||
115 | + slot: "operate", | ||
116 | + width: "150", | ||
117 | + }, | ||
118 | + ], | ||
119 | + }; | ||
120 | + }, | ||
121 | + mounted() { | ||
122 | + this.requestPageData = this.getUserList; | ||
123 | + this.getUserList(); | ||
124 | + }, | ||
125 | + | ||
126 | + methods: { | ||
127 | + async getUserList() { | ||
128 | + this.listLoading = true; | ||
129 | + try { | ||
130 | + await this.$store.dispatch("getUserList", { | ||
131 | + keyword: this.searchForm.keyword, | ||
132 | + pageindex: this.pageInfo.pageNum, | ||
133 | + pagesize: this.pageInfo.pageSize, | ||
134 | + }); | ||
135 | + this.listLoading = false; | ||
136 | + } catch (e) { | ||
137 | + this.listLoading = false; | ||
138 | + } | ||
139 | + }, | ||
140 | + handleCopyId() { | ||
141 | + let clipboard = new Clipboard(".clipboardBtn"); | ||
142 | + clipboard.on("success", (e) => { | ||
143 | + this.$message.success("复制成功"); | ||
144 | + clipboard.destroy(); | ||
145 | + }); | ||
146 | + clipboard.on("error", (e) => { | ||
147 | + this.$message.error("该浏览器不支持自动复制"); | ||
148 | + clipboard.destroy(); | ||
149 | + }); | ||
150 | + }, | ||
151 | + handleClose() { | ||
152 | + this.editShow = false; | ||
153 | + }, | ||
154 | + handleDelete(row) { | ||
155 | + this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", { | ||
156 | + confirmButtonText: "确定", | ||
157 | + cancelButtonText: "取消", | ||
158 | + type: "warning", | ||
159 | + }) | ||
160 | + .then(async () => { | ||
161 | + try { | ||
162 | + await this.$store.dispatch("delUser", row._id); | ||
163 | + this.$message.success("删除成功"); | ||
164 | + this.getUserList(); | ||
165 | + } catch (e) { | ||
166 | + this.$message.error("删除失败"); | ||
167 | + console.log(e); | ||
168 | + } | ||
169 | + }) | ||
170 | + .catch(() => { | ||
171 | + this.$message.info("已取消删除"); | ||
172 | + }); | ||
173 | + }, | ||
174 | + handleEdit(row) { | ||
175 | + this.editShow = true; | ||
176 | + row.releaseTime = new Date(row.releaseTime); | ||
177 | + this.userInfo = row; | ||
178 | + }, | ||
179 | + }, | ||
180 | +}; | ||
181 | +</script> | ||
182 | + | ||
183 | +<style lang="less" scoped> | ||
184 | +.tag { | ||
185 | + margin: 0 10px; | ||
186 | +} | ||
187 | +</style> |
rf-blog/code/server/app.js
0 → 100644
1 | +import Koa from 'koa' | ||
2 | +import ip from 'ip' | ||
3 | +import conf from './config' | ||
4 | +import router from './router' | ||
5 | +import middleware from './middleware' | ||
6 | +import './mongodb' | ||
7 | + | ||
8 | +const app = new Koa() | ||
9 | +middleware(app) | ||
10 | +router(app) | ||
11 | +app.listen(conf.port, '0.0.0.0', () => { | ||
12 | + console.log(`server is running at http://${ip.address()}:${conf.port}`) | ||
13 | +}) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/server/config.js
0 → 100644
1 | +import path from "path"; | ||
2 | +const auth = { | ||
3 | + admin_secret: "admin-token", | ||
4 | + tokenKey: "Token-Auth", | ||
5 | + whiteList: ["login", "client_api"], | ||
6 | + blackList: ["admin_api"], | ||
7 | +}; | ||
8 | + | ||
9 | +const log = { | ||
10 | + logLevel: "debug", // 指定记录的日志级别 | ||
11 | + dir: path.resolve(__dirname, "../../logs"), // 指定日志存放的目录名 | ||
12 | + projectName: "blog", // 项目名,记录在日志中的项目信息 | ||
13 | + ip: "0.0.0.0", // 默认情况下服务器 ip 地址 | ||
14 | +}; | ||
15 | +const port = process.env.NODE_ENV === "production" ? "80" : "3000"; | ||
16 | + | ||
17 | +export default { | ||
18 | + env: process.env.NODE_ENV, | ||
19 | + port, | ||
20 | + auth, | ||
21 | + log, | ||
22 | + mongodb: { | ||
23 | + username: "rf", | ||
24 | + pwd: 123456, | ||
25 | + address: "localhost:27017", | ||
26 | + db: "rfBlog", | ||
27 | + }, | ||
28 | +}; |
rf-blog/code/server/controller/admin/blog.js
0 → 100644
1 | +import blogModel from "../../models/blog"; | ||
2 | +import marked from "marked"; | ||
3 | + | ||
4 | +marked.setOptions({ | ||
5 | + renderer: new marked.Renderer(), | ||
6 | + gfm: true, //允许 Git Hub标准的markdown. | ||
7 | + tables: true, //允许支持表格语法。该选项要求 gfm 为true。 | ||
8 | + breaks: true, //允许回车换行。该选项要求 gfm 为true。 | ||
9 | + pedantic: false, //尽可能地兼容 markdown.pl的晦涩部分。不纠正原始模型任何的不良行为和错误。 | ||
10 | + sanitize: true, //对输出进行过滤(清理),将忽略任何已经输入的html代码(标签) | ||
11 | + smartLists: true, //使用比原生markdown更时髦的列表。 旧的列表将可能被作为pedantic的处理内容过滤掉. | ||
12 | + smartypants: false, //使用更为时髦的标点,比如在引用语法中加入破折号。 | ||
13 | + highlight: function(code) { | ||
14 | + return require("highlight.js").highlightAuto(code).value; | ||
15 | + }, | ||
16 | +}); | ||
17 | + | ||
18 | +module.exports = { | ||
19 | + async list(ctx, next) { | ||
20 | + console.log( | ||
21 | + "----------------获取博客列表 blog/list-----------------------" | ||
22 | + ); | ||
23 | + let { keyword, pageindex = 1, pagesize = 10 } = ctx.request.query; | ||
24 | + console.log("ctx.request =>", ctx.request); | ||
25 | + console.log( | ||
26 | + "keyword:" + | ||
27 | + keyword + | ||
28 | + "," + | ||
29 | + "pageindex:" + | ||
30 | + pageindex + | ||
31 | + "," + | ||
32 | + "pagesize:" + | ||
33 | + pagesize | ||
34 | + ); | ||
35 | + try { | ||
36 | + let reg = new RegExp(keyword, "i"); | ||
37 | + let data = await ctx.findPage( | ||
38 | + blogModel, | ||
39 | + { | ||
40 | + $or: [{ type: { $regex: reg } }, { title: { $regex: reg } }], | ||
41 | + }, | ||
42 | + null, | ||
43 | + { limit: pagesize * 1, skip: (pageindex - 1) * pagesize } | ||
44 | + ); | ||
45 | + ctx.send(data); | ||
46 | + } catch (e) { | ||
47 | + console.log(e); | ||
48 | + ctx.sendError(e); | ||
49 | + } | ||
50 | + }, | ||
51 | + | ||
52 | + async add(ctx, next) { | ||
53 | + console.log("----------------添加博客 blog/add-----------------------"); | ||
54 | + let paramsData = ctx.request.body; | ||
55 | + try { | ||
56 | + let data = await ctx.findOne(blogModel, { title: paramsData.title }); | ||
57 | + if (data) { | ||
58 | + ctx.sendError("数据已经存在, 请重新添加!"); | ||
59 | + } else { | ||
60 | + paramsData.html = marked(paramsData.html); | ||
61 | + let data = await ctx.add(blogModel, paramsData); | ||
62 | + ctx.send(paramsData); | ||
63 | + } | ||
64 | + } catch (e) { | ||
65 | + ctx.sendError(e); | ||
66 | + } | ||
67 | + }, | ||
68 | + | ||
69 | + async update(ctx, next) { | ||
70 | + console.log("----------------更新博客 blog/update-----------------------"); | ||
71 | + let paramsData = ctx.request.body; | ||
72 | + try { | ||
73 | + paramsData.html = marked(paramsData.html); | ||
74 | + let data = await ctx.update( | ||
75 | + blogModel, | ||
76 | + { _id: paramsData._id }, | ||
77 | + paramsData | ||
78 | + ); | ||
79 | + ctx.send(); | ||
80 | + } catch (e) { | ||
81 | + if (e === "暂无数据") { | ||
82 | + ctx.sendError(e); | ||
83 | + } | ||
84 | + } | ||
85 | + }, | ||
86 | + | ||
87 | + async del(ctx, next) { | ||
88 | + console.log("----------------删除博客 blog/del-----------------------"); | ||
89 | + let id = ctx.request.query.id; | ||
90 | + try { | ||
91 | + ctx.remove(blogModel, { _id: id }); | ||
92 | + ctx.send(); | ||
93 | + } catch (e) { | ||
94 | + ctx.sendError(e); | ||
95 | + } | ||
96 | + }, | ||
97 | + | ||
98 | + async info(ctx, next) { | ||
99 | + console.log( | ||
100 | + "----------------获取博客信息 blog/info-----------------------" | ||
101 | + ); | ||
102 | + let _id = ctx.request.query._id; | ||
103 | + try { | ||
104 | + let data = await ctx.findOne(blogModel, { _id }); | ||
105 | + return ctx.send(data); | ||
106 | + } catch (e) { | ||
107 | + return ctx.sendError(e); | ||
108 | + } | ||
109 | + }, | ||
110 | +}; |
1 | +import labelModel from "../../models/label"; | ||
2 | + | ||
3 | +module.exports = { | ||
4 | + async list(ctx, next) { | ||
5 | + console.log( | ||
6 | + "----------------获取标签列表 label/list-----------------------" | ||
7 | + ); | ||
8 | + let { keyword, pageindex = 1, pagesize = 50 } = ctx.request.query; | ||
9 | + try { | ||
10 | + let reg = new RegExp(keyword, "i"); | ||
11 | + let data = await ctx.findPage( | ||
12 | + labelModel, | ||
13 | + { | ||
14 | + $or: [{ label: { $regex: reg } }, { bgColor: { $regex: reg } }], | ||
15 | + }, | ||
16 | + null, | ||
17 | + { limit: pagesize * 1, skip: (pageindex - 1) * pagesize } | ||
18 | + ); | ||
19 | + ctx.send(data); | ||
20 | + } catch (e) { | ||
21 | + console.log(e); | ||
22 | + ctx.sendError(e); | ||
23 | + } | ||
24 | + }, | ||
25 | + | ||
26 | + async add(ctx, next) { | ||
27 | + console.log("----------------添加标签 label/add-----------------------"); | ||
28 | + let paramsData = ctx.request.body; | ||
29 | + try { | ||
30 | + let data = await ctx.findOne(labelModel, { label: paramsData.label }); | ||
31 | + if (data) { | ||
32 | + ctx.sendError("数据已经存在, 请重新添加!"); | ||
33 | + } else { | ||
34 | + let result = await ctx.add(labelModel, paramsData); | ||
35 | + ctx.send(result); | ||
36 | + } | ||
37 | + } catch (e) { | ||
38 | + ctx.sendError(e); | ||
39 | + } | ||
40 | + }, | ||
41 | + | ||
42 | + async update(ctx, next) { | ||
43 | + console.log("----------------更新标签 label/update-----------------------"); | ||
44 | + let paramsData = ctx.request.body; | ||
45 | + try { | ||
46 | + let data = await ctx.update( | ||
47 | + labelModel, | ||
48 | + { _id: paramsData._id }, | ||
49 | + paramsData | ||
50 | + ); | ||
51 | + ctx.send(data); | ||
52 | + } catch (e) { | ||
53 | + if (e === "暂无数据") { | ||
54 | + ctx.sendError(e); | ||
55 | + } | ||
56 | + } | ||
57 | + }, | ||
58 | + | ||
59 | + async del(ctx, next) { | ||
60 | + console.log("----------------删除标签 label/del-----------------------"); | ||
61 | + let id = ctx.request.query.id; | ||
62 | + try { | ||
63 | + let data = await ctx.remove(labelModel, { _id: id }); | ||
64 | + ctx.send(data); | ||
65 | + } catch (e) { | ||
66 | + ctx.sendError(e); | ||
67 | + } | ||
68 | + }, | ||
69 | +}; |
1 | +import messageModel from '../../models/message'; | ||
2 | + | ||
3 | +module.exports = { | ||
4 | + async list(ctx, next) { | ||
5 | + console.log( | ||
6 | + '----------------获取留言列表 admin_api/message/list-----------------------' | ||
7 | + ); | ||
8 | + let { keyword, pageindex = 1, pagesize = 10 } = ctx.request.query; | ||
9 | + | ||
10 | + let reg = new RegExp(keyword, 'i'); | ||
11 | + | ||
12 | + let conditions = { | ||
13 | + $or: [{ nickname: { $regex: reg } }, { content: { $regex: reg } }], | ||
14 | + }; | ||
15 | + | ||
16 | + // 排序参数 | ||
17 | + let sortParams = { | ||
18 | + createTime: -1, | ||
19 | + }; | ||
20 | + | ||
21 | + let options = { | ||
22 | + limit: pagesize * 1, | ||
23 | + skip: (pageindex - 1) * pagesize, | ||
24 | + sort: sortParams, | ||
25 | + }; | ||
26 | + | ||
27 | + try { | ||
28 | + let data = await ctx.find(messageModel, conditions, null, options); | ||
29 | + return ctx.send(data); | ||
30 | + } catch (e) { | ||
31 | + console.log(e); | ||
32 | + return ctx.sendError(e); | ||
33 | + } | ||
34 | + }, | ||
35 | + | ||
36 | + async del(ctx, next) { | ||
37 | + console.log( | ||
38 | + '----------------删除留言 admin_api/message/del-----------------------' | ||
39 | + ); | ||
40 | + let id = ctx.request.query.id; | ||
41 | + try { | ||
42 | + ctx.remove(messageModel, { _id: id }); | ||
43 | + ctx.send(); | ||
44 | + } catch (e) { | ||
45 | + ctx.sendError(e); | ||
46 | + } | ||
47 | + }, | ||
48 | + | ||
49 | + async delReply(ctx, next) { | ||
50 | + console.log( | ||
51 | + '----------------删除回复 admin_api/message/delReply-----------------------' | ||
52 | + ); | ||
53 | + let { _id } = ctx.request.body; | ||
54 | + let options = { | ||
55 | + $pull: { replyList: { _id } }, | ||
56 | + }; | ||
57 | + try { | ||
58 | + let data = await ctx.update(messageModel, { _id }, options); | ||
59 | + ctx.send(); | ||
60 | + } catch (e) { | ||
61 | + ctx.sendError(e); | ||
62 | + } | ||
63 | + }, | ||
64 | +}; |
1 | +const path = require("path"); | ||
2 | + | ||
3 | +module.exports = { | ||
4 | + async uploadImage(ctx, next) { | ||
5 | + console.log("----------------添加图片 uploadImage-----------------------"); | ||
6 | + try { | ||
7 | + let opts = { | ||
8 | + path: path.resolve(__dirname, "../../../../public"), | ||
9 | + }; | ||
10 | + let result = await ctx.uploadFile(ctx, opts); | ||
11 | + ctx.send(result); | ||
12 | + } catch (e) { | ||
13 | + ctx.sendError(e); | ||
14 | + } | ||
15 | + }, | ||
16 | + async delUploadImage(ctx, next) { | ||
17 | + console.log( | ||
18 | + "----------------删除图片 delUploadImage-----------------------" | ||
19 | + ); | ||
20 | + let fileName = ctx.request.body.fileName; | ||
21 | + let fileCoverImgUrl = `public/images/${fileName}`; | ||
22 | + try { | ||
23 | + ctx.removeFile(fileCoverImgUrl); | ||
24 | + ctx.send(); | ||
25 | + } catch (e) { | ||
26 | + ctx.sendError(e); | ||
27 | + } | ||
28 | + }, | ||
29 | +}; |
rf-blog/code/server/controller/admin/user.js
0 → 100644
1 | +import jwt from "jsonwebtoken"; | ||
2 | +import conf from "../../config"; | ||
3 | +import userModel from "../../models/user"; | ||
4 | +module.exports = { | ||
5 | + async login(ctx, next) { | ||
6 | + console.log("----------------登录接口 user/login-----------------------"); | ||
7 | + let { username, pwd } = ctx.request.body; | ||
8 | + try { | ||
9 | + let data = await ctx.findOne(userModel, { username: username }); | ||
10 | + console.log(data); | ||
11 | + if (!data) { | ||
12 | + return ctx.sendError("用户名不存在!"); | ||
13 | + } | ||
14 | + if (pwd !== data.pwd) { | ||
15 | + return ctx.sendError("密码错误,请重新输入!"); | ||
16 | + } | ||
17 | + await ctx.update( | ||
18 | + userModel, | ||
19 | + { _id: data._id }, | ||
20 | + { $set: { loginTime: new Date() } } | ||
21 | + ); //更新登陆时间 | ||
22 | + | ||
23 | + let payload = { | ||
24 | + _id: data._id, | ||
25 | + username: data.username, | ||
26 | + roles: data.roles, | ||
27 | + }; | ||
28 | + // token签名 有效期为24小时 | ||
29 | + let token = jwt.sign(payload, conf.auth.admin_secret, { | ||
30 | + expiresIn: "24h", | ||
31 | + }); | ||
32 | + // 是否只用于http请求中获取 | ||
33 | + ctx.cookies.set(conf.auth.tokenKey, token, { | ||
34 | + httpOnly: false, | ||
35 | + }); | ||
36 | + ctx.send({ message: "登录成功" }); | ||
37 | + } catch (e) { | ||
38 | + if (e === "暂无数据") { | ||
39 | + console.log("用户名不存在"); | ||
40 | + return ctx.sendError("用户名不存在"); | ||
41 | + } | ||
42 | + ctx.throw(e); | ||
43 | + ctx.sendError(e); | ||
44 | + } | ||
45 | + }, | ||
46 | + async info(ctx, next) { | ||
47 | + console.log( | ||
48 | + "----------------获取用户信息接口 user/getUserInfo-----------------------" | ||
49 | + ); | ||
50 | + let token = ctx.request.query.token; | ||
51 | + try { | ||
52 | + let tokenInfo = jwt.verify(token, conf.auth.admin_secret); | ||
53 | + console.log("log tokenInfo =>", tokenInfo); | ||
54 | + ctx.send({ | ||
55 | + username: tokenInfo.username, | ||
56 | + _id: tokenInfo._id, | ||
57 | + roles: tokenInfo.roles, | ||
58 | + }); | ||
59 | + } catch (e) { | ||
60 | + if ("TokenExpiredError" === e.name) { | ||
61 | + ctx.sendError("鉴权失败, 请重新登录!"); | ||
62 | + ctx.throw(401, "token验证失败, 请重新登录!"); | ||
63 | + } | ||
64 | + ctx.throw(401, "invalid token"); | ||
65 | + ctx.sendError("系统异常!"); | ||
66 | + } | ||
67 | + }, | ||
68 | + | ||
69 | + async list(ctx, next) { | ||
70 | + console.log( | ||
71 | + "----------------获取用户信息列表接口 user/getUserList-----------------------" | ||
72 | + ); | ||
73 | + let { keyword, pageindex = 1, pagesize = 10 } = ctx.request.query; | ||
74 | + console.log( | ||
75 | + "keyword:" + | ||
76 | + keyword + | ||
77 | + "," + | ||
78 | + "pageindex:" + | ||
79 | + pageindex + | ||
80 | + "," + | ||
81 | + "pagesize:" + | ||
82 | + pagesize | ||
83 | + ); | ||
84 | + | ||
85 | + try { | ||
86 | + let reg = new RegExp(keyword, "i"); | ||
87 | + let data = await ctx.findPage( | ||
88 | + userModel, | ||
89 | + { | ||
90 | + $or: [{ username: { $regex: reg } }], | ||
91 | + }, | ||
92 | + { pwd: 0 }, | ||
93 | + { limit: pagesize * 1, skip: (pageindex - 1) * pagesize } | ||
94 | + ); | ||
95 | + | ||
96 | + ctx.send(data); | ||
97 | + } catch (e) { | ||
98 | + console.log(e); | ||
99 | + ctx.sendError(e); | ||
100 | + } | ||
101 | + }, | ||
102 | + | ||
103 | + async add(ctx, next) { | ||
104 | + console.log("----------------添加管理员 user/add-----------------------"); | ||
105 | + let paramsData = ctx.request.body; | ||
106 | + try { | ||
107 | + let data = await ctx.findOne(userModel, { | ||
108 | + username: paramsData.username, | ||
109 | + }); | ||
110 | + if (data) { | ||
111 | + ctx.sendError("数据已经存在, 请重新添加!"); | ||
112 | + } else { | ||
113 | + await ctx.add(userModel, paramsData); | ||
114 | + ctx.send(paramsData); | ||
115 | + } | ||
116 | + } catch (e) { | ||
117 | + ctx.sendError(e); | ||
118 | + } | ||
119 | + }, | ||
120 | + | ||
121 | + async update(ctx, next) { | ||
122 | + console.log( | ||
123 | + "----------------更新管理员 user/update-----------------------" | ||
124 | + ); | ||
125 | + let paramsData = ctx.request.body; | ||
126 | + console.log(paramsData); | ||
127 | + try { | ||
128 | + let data = await ctx.findOne(userModel, { | ||
129 | + username: paramsData.username, | ||
130 | + }); | ||
131 | + if (paramsData.old_pwd !== data.pwd) { | ||
132 | + return ctx.sendError("密码不匹配!"); | ||
133 | + } | ||
134 | + delete paramsData.old_pwd; | ||
135 | + await ctx.update(userModel, { _id: paramsData._id }, paramsData); | ||
136 | + ctx.send(); | ||
137 | + } catch (e) { | ||
138 | + if (e === "暂无数据") { | ||
139 | + ctx.sendError(e); | ||
140 | + } | ||
141 | + } | ||
142 | + }, | ||
143 | + | ||
144 | + async del(ctx, next) { | ||
145 | + console.log("----------------删除管理员 user/del-----------------------"); | ||
146 | + let id = ctx.request.query.id; | ||
147 | + try { | ||
148 | + ctx.remove(userModel, { _id: id }); | ||
149 | + ctx.send(); | ||
150 | + } catch (e) { | ||
151 | + ctx.sendError(e); | ||
152 | + } | ||
153 | + }, | ||
154 | +}; |
rf-blog/code/server/index.js
0 → 100644
rf-blog/code/server/middleware/auth/index.js
0 → 100644
1 | +import jwt from 'jsonwebtoken'; | ||
2 | +import conf from '../../config'; | ||
3 | + | ||
4 | +export default () => { | ||
5 | + return async (ctx, next) => { | ||
6 | + // 白名单就不需要走 jwt 鉴权 | ||
7 | + if (!conf.auth.whiteList.some((v) => ctx.path.includes(v))) { | ||
8 | + let token = ctx.cookies.get(conf.auth.tokenKey); | ||
9 | + try { | ||
10 | + jwt.verify(token, conf.auth.admin_secret); | ||
11 | + } catch (e) { | ||
12 | + if ('TokenExpiredError' === e.name) { | ||
13 | + ctx.sendError('token已过期, 请重新登录!'); | ||
14 | + ctx.throw(401, 'token已过期, 请重新登录!'); | ||
15 | + } | ||
16 | + ctx.sendError('token验证失败, 请重新登录!'); | ||
17 | + ctx.throw(401, 'token验证失败, 请重新登录!'); | ||
18 | + } | ||
19 | + console.log('鉴权成功'); | ||
20 | + } | ||
21 | + await next(); | ||
22 | + }; | ||
23 | +}; |
rf-blog/code/server/middleware/func/db.js
0 → 100644
1 | +/* | ||
2 | + * 公共Add方法 | ||
3 | + * @param model 要操作数据库的模型 | ||
4 | + * @param conditions 增加的条件,如{id:xxx} | ||
5 | + */ | ||
6 | +export const add = (model, conditions) => { | ||
7 | + return new Promise((resolve, reject) => { | ||
8 | + model.create(conditions, (err, res) => { | ||
9 | + if (err) { | ||
10 | + console.error("Error: " + JSON.stringify(err)); | ||
11 | + reject(err); | ||
12 | + return false; | ||
13 | + } | ||
14 | + console.log("save success!"); | ||
15 | + resolve(res); | ||
16 | + }); | ||
17 | + }); | ||
18 | +}; | ||
19 | + | ||
20 | +/* | ||
21 | + * 公共update方法 | ||
22 | + * @param model 要操作数据库的模型 | ||
23 | + * @param conditions 增加的条件,如{id:xxx} | ||
24 | + * @param update 更新条件{set{id:xxx}} | ||
25 | + * @param options | ||
26 | + */ | ||
27 | +export const update = (model, conditions, update, options) => { | ||
28 | + return new Promise((resolve, reject) => { | ||
29 | + model.update(conditions, update, options, (err, res) => { | ||
30 | + if (err) { | ||
31 | + console.error("Error: " + JSON.stringify(err)); | ||
32 | + reject(err); | ||
33 | + return false; | ||
34 | + } | ||
35 | + if (res.n !== 0) { | ||
36 | + console.log("update success!"); | ||
37 | + } else { | ||
38 | + console.log("update fail:no this data!"); | ||
39 | + return reject("update fail:no this data!"); | ||
40 | + } | ||
41 | + resolve(res); | ||
42 | + }); | ||
43 | + }); | ||
44 | +}; | ||
45 | + | ||
46 | +/** | ||
47 | + * 公共remove方法 | ||
48 | + * @param model | ||
49 | + * @param conditions | ||
50 | + */ | ||
51 | + | ||
52 | +export const remove = (model, conditions) => { | ||
53 | + return new Promise((resolve, reject) => { | ||
54 | + model.remove(conditions, function(err, res) { | ||
55 | + if (err) { | ||
56 | + console.error("Error: " + JSON.stringify(err)); | ||
57 | + reject(err); | ||
58 | + return false; | ||
59 | + } else { | ||
60 | + if (res.result.n !== 0) { | ||
61 | + console.log("remove success!"); | ||
62 | + } else { | ||
63 | + console.log("remove fail:no this data!"); | ||
64 | + } | ||
65 | + resolve(res); | ||
66 | + } | ||
67 | + }); | ||
68 | + }); | ||
69 | +}; | ||
70 | + | ||
71 | +/** | ||
72 | + * 公共find方法 非关联查找 | ||
73 | + * @param model | ||
74 | + * @param conditions | ||
75 | + * @param fields 查找时限定的条件,如顺序,某些字段不查找等 | ||
76 | + * @param options | ||
77 | + * @param callback | ||
78 | + */ | ||
79 | +export const find = async (model, conditions, fields, options = {}) => { | ||
80 | + let { sort } = options; | ||
81 | + delete options.sort; | ||
82 | + | ||
83 | + const getCount = () => { | ||
84 | + return new Promise((resolve, reject) => { | ||
85 | + model.find(conditions, fields).count({}, (err, res) => { | ||
86 | + if (err) { | ||
87 | + console.log("查询长度错误"); | ||
88 | + return reject(err); | ||
89 | + } | ||
90 | + | ||
91 | + resolve(res); | ||
92 | + }); | ||
93 | + }); | ||
94 | + }; | ||
95 | + | ||
96 | + const count = await getCount(); | ||
97 | + | ||
98 | + return new Promise((resolve, reject) => { | ||
99 | + model | ||
100 | + .find(conditions, fields, options, function(err, res) { | ||
101 | + if (err) { | ||
102 | + console.error("Error: " + JSON.stringify(err)); | ||
103 | + reject(err); | ||
104 | + return false; | ||
105 | + } else { | ||
106 | + if (res.length !== 0) { | ||
107 | + resolve({ | ||
108 | + list: res, | ||
109 | + total: count, | ||
110 | + }); | ||
111 | + console.log("find success!"); | ||
112 | + } else { | ||
113 | + console.log("find fail:no this data!"); | ||
114 | + } | ||
115 | + // resolve(res); | ||
116 | + resolve({ | ||
117 | + list: res, | ||
118 | + total: count, | ||
119 | + }); | ||
120 | + } | ||
121 | + }) | ||
122 | + .sort(sort); | ||
123 | + }); | ||
124 | +}; | ||
125 | + | ||
126 | +/** | ||
127 | + * 公共findOne方法 非关联查找 | ||
128 | + * @param model | ||
129 | + * @param conditions | ||
130 | + * @param fields 查找时限定的条件,如顺序,某些字段不查找等 | ||
131 | + * @param options | ||
132 | + * @param callback | ||
133 | + */ | ||
134 | +export const findOne = (model, conditions, fields, options = {}) => { | ||
135 | + let { sort } = options; | ||
136 | + delete options.sort; | ||
137 | + return new Promise((resolve, reject) => { | ||
138 | + model | ||
139 | + .findOne(conditions, fields, options, function(err, res) { | ||
140 | + if (err) { | ||
141 | + console.error("Error: " + JSON.stringify(err)); | ||
142 | + reject(err); | ||
143 | + return false; | ||
144 | + } else { | ||
145 | + if (res) { | ||
146 | + console.log("find success!"); | ||
147 | + } else { | ||
148 | + console.log("find fail:no this data!"); | ||
149 | + } | ||
150 | + resolve(res); | ||
151 | + } | ||
152 | + }) | ||
153 | + .sort(sort); | ||
154 | + }); | ||
155 | +}; | ||
156 | + | ||
157 | +export const findPage = async (model, conditions, fields, options = {}) => { | ||
158 | + let { sort } = options; | ||
159 | + delete options.sort; | ||
160 | + | ||
161 | + const getCount = () => { | ||
162 | + return new Promise((resolve, reject) => { | ||
163 | + model.find(conditions, fields).count({}, (err, res) => { | ||
164 | + if (err) { | ||
165 | + console.log("查询长度错误"); | ||
166 | + return reject(err); | ||
167 | + } | ||
168 | + resolve(res); | ||
169 | + }); | ||
170 | + }); | ||
171 | + }; | ||
172 | + | ||
173 | + const count = await getCount(); | ||
174 | + | ||
175 | + return new Promise((resolve, reject) => { | ||
176 | + model.find(conditions, fields, options, function(err, res) { | ||
177 | + if (err) { | ||
178 | + console.error("Error: " + JSON.stringify(err)); | ||
179 | + reject(err); | ||
180 | + return false; | ||
181 | + } else { | ||
182 | + if (res.length !== 0) { | ||
183 | + console.log("find success!"); | ||
184 | + resolve({ | ||
185 | + list: res, | ||
186 | + total: count, | ||
187 | + }); | ||
188 | + } else { | ||
189 | + console.log("find fail:no this data!"); | ||
190 | + resolve({ | ||
191 | + list: res, | ||
192 | + total: count, | ||
193 | + }); | ||
194 | + } | ||
195 | + } | ||
196 | + }); | ||
197 | + }); | ||
198 | +}; | ||
199 | + | ||
200 | +/* | ||
201 | + * 公共aggregate方法 | ||
202 | + * @param model 要操作数据库的模型 | ||
203 | + * @param conditions 增加的条件,如{id:xxx} | ||
204 | + */ | ||
205 | +export const aggregate = (model, conditions) => { | ||
206 | + return new Promise((resolve, reject) => { | ||
207 | + model.aggregate(conditions, (err, res) => { | ||
208 | + if (err) { | ||
209 | + console.error("Error: " + JSON.stringify(err)); | ||
210 | + reject(err); | ||
211 | + return false; | ||
212 | + } | ||
213 | + console.log("aggregate success!"); | ||
214 | + resolve(res); | ||
215 | + }); | ||
216 | + }); | ||
217 | +}; |
rf-blog/code/server/middleware/func/file.js
0 → 100644
1 | +import Busboy from "busboy"; | ||
2 | +import fs from "fs"; | ||
3 | +import path from "path"; | ||
4 | + | ||
5 | +//检测文件并创建文件 | ||
6 | +const mkdirSync = (dirname) => { | ||
7 | + if (fs.existsSync(dirname)) { | ||
8 | + return true; | ||
9 | + } else { | ||
10 | + if (mkdirSync(path.dirname(dirname))) { | ||
11 | + fs.mkdirSync(dirname); | ||
12 | + return true; | ||
13 | + } | ||
14 | + } | ||
15 | +}; | ||
16 | +// 删除本地图片 | ||
17 | +export const removeFile = (filePath) => { | ||
18 | + fs.unlink(filePath, function(err) { | ||
19 | + if (err) { | ||
20 | + throw err; | ||
21 | + } | ||
22 | + console.log("文件:" + filePath + "删除成功!"); | ||
23 | + }); | ||
24 | +}; | ||
25 | + | ||
26 | +export const uploadFile = (ctx, opts) => { | ||
27 | + //重命名 | ||
28 | + function rename(fileName) { | ||
29 | + return ( | ||
30 | + Math.random() | ||
31 | + .toString(16) | ||
32 | + .substr(2) + | ||
33 | + "." + | ||
34 | + fileName.split(".").pop() | ||
35 | + ); | ||
36 | + } | ||
37 | + let busboy = new Busboy({ headers: ctx.req.headers }); | ||
38 | + console.log("start uploading..."); | ||
39 | + /* | ||
40 | + filename: 字段名, | ||
41 | + file: 文件流, | ||
42 | + filename: 文件名 | ||
43 | + */ | ||
44 | + return new Promise((resolve, reject) => { | ||
45 | + var fileObj = {}; | ||
46 | + busboy.on("file", async (fieldname, file, filename, encoding, mimetype) => { | ||
47 | + let filePath = "", | ||
48 | + imgPrefix = ""; | ||
49 | + | ||
50 | + filePath = path.join(opts.path, mimetype.split("/")[0] + "s"); | ||
51 | + // 现网图片路径不一样 | ||
52 | + imgPrefix = `${ctx.protocol}://${ctx.host}/${mimetype.split("/")[0]}s`; | ||
53 | + | ||
54 | + if (!mkdirSync(filePath)) { | ||
55 | + throw new Error("没找到目录"); | ||
56 | + } | ||
57 | + let fName = rename(filename), | ||
58 | + fPath = path.join(path.join(filePath, fName)); | ||
59 | + file.pipe(fs.createWriteStream(fPath)); | ||
60 | + | ||
61 | + console.log("fName =>", fName); | ||
62 | + console.log("fPath =>", fPath); | ||
63 | + | ||
64 | + file.on("end", () => { | ||
65 | + fileObj[fieldname] = `${imgPrefix}/${fName}`; | ||
66 | + }); | ||
67 | + }); | ||
68 | + | ||
69 | + busboy.on( | ||
70 | + "field", | ||
71 | + ( | ||
72 | + fieldname, | ||
73 | + val, | ||
74 | + fieldnameTruncated, | ||
75 | + valTruncated, | ||
76 | + encoding, | ||
77 | + mimetype | ||
78 | + ) => { | ||
79 | + fileObj[fieldname] = val; | ||
80 | + } | ||
81 | + ); | ||
82 | + | ||
83 | + busboy.on("finish", async () => { | ||
84 | + resolve(fileObj); | ||
85 | + console.log("finished...", fileObj); | ||
86 | + }); | ||
87 | + busboy.on("error", function(err) { | ||
88 | + console.log("err:" + err); | ||
89 | + reject(err); | ||
90 | + }); | ||
91 | + | ||
92 | + ctx.req.pipe(busboy); | ||
93 | + }); | ||
94 | +}; |
1 | +export const get_client_ip = (ctx) => { | ||
2 | + return ( | ||
3 | + ctx.request.headers["x-forwarded-for"] || | ||
4 | + (ctx.request.connection && ctx.request.connection.remoteAddress) || | ||
5 | + ctx.request.socket.remoteAddress || | ||
6 | + (ctx.request.connection.socket && | ||
7 | + ctx.request.connection.socket.remoteAddress) || | ||
8 | + null | ||
9 | + ); | ||
10 | +}; |
rf-blog/code/server/middleware/func/index.js
0 → 100644
1 | +import * as get_Info_func from "./get_info"; | ||
2 | +import * as db_func from "./db"; | ||
3 | +import * as file_func from "./file"; | ||
4 | + | ||
5 | +export default () => { | ||
6 | + const func = Object.assign({}, get_Info_func, db_func, file_func); | ||
7 | + return async (ctx, next) => { | ||
8 | + for (let v in func) { | ||
9 | + if (func.hasOwnProperty(v)) ctx[v] = func[v]; | ||
10 | + } | ||
11 | + await next(); | ||
12 | + }; | ||
13 | +}; |
rf-blog/code/server/middleware/index.js
0 → 100644
1 | +import path from "path"; | ||
2 | +import bodyParser from "koa-bodyparser"; | ||
3 | +import staticFiles from "koa-static"; | ||
4 | +import Rule from "./rule"; | ||
5 | +import Send from "./send"; | ||
6 | +import Auth from "./auth"; | ||
7 | +import Log from "./log"; | ||
8 | +import Func from "./func"; | ||
9 | + | ||
10 | +export default (app) => { | ||
11 | + //缓存拦截器 | ||
12 | + app.use(async (ctx, next) => { | ||
13 | + if (ctx.url == "/favicon.ico") return; | ||
14 | + | ||
15 | + await next(); | ||
16 | + ctx.status = 200; | ||
17 | + ctx.set("Cache-Control", "must-revalidation"); | ||
18 | + if (ctx.fresh) { | ||
19 | + ctx.status = 304; | ||
20 | + return; | ||
21 | + } | ||
22 | + }); | ||
23 | + | ||
24 | + // 日志中间件 | ||
25 | + app.use(Log()); | ||
26 | + | ||
27 | + // 数据返回的封装 | ||
28 | + app.use(Send()); | ||
29 | + | ||
30 | + // 方法封装 | ||
31 | + app.use(Func()); | ||
32 | + | ||
33 | + //权限中间件 | ||
34 | + app.use(Auth()); | ||
35 | + | ||
36 | + //post请求中间件 | ||
37 | + app.use(bodyParser()); | ||
38 | + | ||
39 | + //静态文件中间件 | ||
40 | + app.use(staticFiles(path.resolve(__dirname, "../../../public"))); | ||
41 | + | ||
42 | + // 规则中间件 | ||
43 | + Rule({ | ||
44 | + app, | ||
45 | + rules: [ | ||
46 | + { | ||
47 | + path: path.join(__dirname, "../controller/admin"), | ||
48 | + name: "admin", | ||
49 | + }, | ||
50 | + { | ||
51 | + path: path.join(__dirname, "../controller/client"), | ||
52 | + name: "client", | ||
53 | + }, | ||
54 | + ], | ||
55 | + }); | ||
56 | + | ||
57 | + // 增加错误的监听处理 | ||
58 | + app.on("error", (err, ctx) => { | ||
59 | + if (ctx && !ctx.headerSent && ctx.status < 500) { | ||
60 | + ctx.status = 500; | ||
61 | + } | ||
62 | + if (ctx && ctx.log && ctx.log.error) { | ||
63 | + if (!ctx.state.logged) { | ||
64 | + ctx.log.error(err.stack); | ||
65 | + } | ||
66 | + } | ||
67 | + }); | ||
68 | +}; |
rf-blog/code/server/middleware/log/access.js
0 → 100644
1 | +export default (ctx, msg, commonInfo) => { | ||
2 | + const { | ||
3 | + method, // 请求方法 get post或其他 | ||
4 | + url, // 请求链接 | ||
5 | + host, // 发送请求的客户端的host | ||
6 | + headers // 请求中的headers | ||
7 | + } = ctx.request; | ||
8 | + const client = { | ||
9 | + method, | ||
10 | + url, | ||
11 | + host, | ||
12 | + msg, | ||
13 | + ip: ctx.get_client_ip(ctx), | ||
14 | + referer: headers['referer'], // 请求的源地址 | ||
15 | + userAgent: headers['user-agent'] // 客户端信息 设备及浏览器信息 | ||
16 | + } | ||
17 | + return JSON.stringify(Object.assign(commonInfo, client)); | ||
18 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/server/middleware/log/index.js
0 → 100644
1 | +import logger from './log' | ||
2 | + | ||
3 | +export default opts => { | ||
4 | + let loggerMiddleware = logger(opts); | ||
5 | + return async (ctx, next) => { | ||
6 | + return loggerMiddleware(ctx, next) | ||
7 | + .catch( e => { | ||
8 | + if (ctx.status < 500) { | ||
9 | + ctx.status = 500; | ||
10 | + } | ||
11 | + ctx.log.error(e.stack); | ||
12 | + ctx.state.logged = true; | ||
13 | + ctx.throw(e); | ||
14 | + }) | ||
15 | + } | ||
16 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/server/middleware/log/log.js
0 → 100644
1 | +import log4js from 'log4js' | ||
2 | +import access from './access' // 引入日志输出信息的封装文件 | ||
3 | +import config from '../../config' | ||
4 | +const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]; | ||
5 | + | ||
6 | +// 提取默认公用参数对象 | ||
7 | +const baseInfo = config.log | ||
8 | +export default (options = {}) => { | ||
9 | + let contextLogger = {}, //错误日志等级对象,最后会赋值给ctx上,用于打印各种日志 | ||
10 | + appenders = {}, //日志配置 | ||
11 | + opts = Object.assign({}, baseInfo, options), //系统配置 | ||
12 | + { | ||
13 | + logLevel, | ||
14 | + dir, | ||
15 | + ip, | ||
16 | + projectName | ||
17 | + } = opts, | ||
18 | + commonInfo = { | ||
19 | + projectName, | ||
20 | + ip | ||
21 | + }; //存储公用的日志信息 | ||
22 | + | ||
23 | + //指定要记录的日志分类 | ||
24 | + appenders.all = { | ||
25 | + type: 'dateFile', //日志文件类型,可以使用日期作为文件名的占位符 | ||
26 | + filename: `${dir}/all/`, //日志文件名,可以设置相对路径或绝对路径 | ||
27 | + pattern: 'task-yyyy-MM-dd.log', //占位符,紧跟在filename后面 | ||
28 | + alwaysIncludePattern: true //是否总是有后缀名 | ||
29 | + } | ||
30 | + | ||
31 | + // 环境变量为dev local development 认为是开发环境 | ||
32 | + if (config.env === "dev" || config.env === "local" || config.env === "development") { | ||
33 | + appenders.out = { | ||
34 | + type: "console" | ||
35 | + } | ||
36 | + } | ||
37 | + | ||
38 | + let logConfig = { | ||
39 | + appenders, | ||
40 | + | ||
41 | + /** | ||
42 | + * 指定日志的默认配置项 | ||
43 | + * 如果 log4js.getLogger 中没有指定,默认为 cheese 日志的配置项 | ||
44 | + */ | ||
45 | + categories: { | ||
46 | + default: { | ||
47 | + appenders: Object.keys(appenders), | ||
48 | + level: logLevel | ||
49 | + } | ||
50 | + } | ||
51 | + } | ||
52 | + | ||
53 | + let logger = log4js.getLogger('cheese'); | ||
54 | + return async (ctx, next) => { | ||
55 | + const start = Date.now() // 记录请求开始的时间 | ||
56 | + | ||
57 | + // 循环methods将所有方法挂载到ctx 上 | ||
58 | + methods.forEach((method, i) => { | ||
59 | + contextLogger[method] = message => { | ||
60 | + logConfig.appenders.cheese = { | ||
61 | + type: 'dateFile', //日志文件类型,可以使用日期作为文件名的占位符 | ||
62 | + filename: `${dir}/${method}/`, | ||
63 | + pattern: `${method}-yyyy-MM-dd.log`, | ||
64 | + alwaysIncludePattern: true //是否总是有后缀名 | ||
65 | + } | ||
66 | + log4js.configure(logConfig) | ||
67 | + logger[method](access(ctx, message, commonInfo)) | ||
68 | + } | ||
69 | + }) | ||
70 | + ctx.log = contextLogger | ||
71 | + await next() | ||
72 | + // 记录完成的时间 作差 计算响应时间 | ||
73 | + const responseTime = Date.now() - start | ||
74 | + | ||
75 | + ctx.log.info(access(ctx, { | ||
76 | + responseTime: `响应时间为${responseTime/1000}s` | ||
77 | + }, commonInfo)) | ||
78 | + | ||
79 | + } | ||
80 | + | ||
81 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
rf-blog/code/server/middleware/rule/index.js
0 → 100644
1 | +import Path from "path"; | ||
2 | +import fs from "fs"; | ||
3 | + | ||
4 | +export default (opts) => { | ||
5 | + let { app, rules = [] } = opts; | ||
6 | + if (!app) { | ||
7 | + throw new Error("the app params is necessary!"); | ||
8 | + } | ||
9 | + | ||
10 | + app.router = {}; | ||
11 | + const appKeys = Object.keys(app); | ||
12 | + rules.forEach((item) => { | ||
13 | + let { path, name } = item; | ||
14 | + if (appKeys.includes(name)) { | ||
15 | + throw new Error(`the name of ${name} already exists!`); | ||
16 | + } | ||
17 | + let content = {}; | ||
18 | + //readdirSync: 方法将返回一个包含“指定目录下所有文件名称”的数组对象。 | ||
19 | + //extname: 返回path路径文件扩展名,如果path以 ‘.' 为结尾,将返回 ‘.',如果无扩展名 又 不以'.'结尾,将返回空值。 | ||
20 | + //basename: path.basename(p, [ext]) p->要处理的path ext->要过滤的字符 | ||
21 | + fs.readdirSync(path).forEach((filename) => { | ||
22 | + let extname = Path.extname(filename); | ||
23 | + if (extname === ".js") { | ||
24 | + let name = Path.basename(filename, extname); | ||
25 | + content[name] = require(Path.join(path, filename)); | ||
26 | + content[name].filename = name; | ||
27 | + } | ||
28 | + }); | ||
29 | + app[name] = content; | ||
30 | + }); | ||
31 | +}; |
rf-blog/code/server/middleware/send/index.js
0 → 100644
1 | +export default () => { | ||
2 | + let render = (ctx) => { | ||
3 | + return (json, msg) => { | ||
4 | + ctx.set("Content-Type", "application/json"); | ||
5 | + ctx.body = JSON.stringify({ | ||
6 | + code: 1, | ||
7 | + data: json || {}, | ||
8 | + msg: msg || "success", | ||
9 | + }); | ||
10 | + }; | ||
11 | + }; | ||
12 | + let renderError = (ctx) => { | ||
13 | + return (msg) => { | ||
14 | + ctx.set("Content-Type", "application/json"); | ||
15 | + ctx.body = JSON.stringify({ | ||
16 | + code: 0, | ||
17 | + data: {}, | ||
18 | + msg: msg.toString(), | ||
19 | + }); | ||
20 | + }; | ||
21 | + }; | ||
22 | + return async (ctx, next) => { | ||
23 | + ctx.send = render(ctx); | ||
24 | + ctx.sendError = renderError(ctx); | ||
25 | + await next(); | ||
26 | + }; | ||
27 | +}; |
rf-blog/code/server/models/blog.js
0 → 100644
1 | +import db from "../mongodb"; | ||
2 | +let blogSchema = db.Schema({ | ||
3 | + type: Array, | ||
4 | + title: String, | ||
5 | + desc: String, | ||
6 | + fileCoverImgUrl: String, | ||
7 | + html: String, | ||
8 | + markdown: String, | ||
9 | + level: Number, | ||
10 | + github: String, | ||
11 | + auth: String, | ||
12 | + source: Number, | ||
13 | + isVisible: Boolean, | ||
14 | + releaseTime: String, | ||
15 | + pv: { type: Number, default: 0 }, | ||
16 | + likes: { type: Number, default: 0 }, | ||
17 | + comments: { type: Number, default: 0 }, | ||
18 | +}); | ||
19 | +export default db.model("blog", blogSchema); |
rf-blog/code/server/models/label.js
0 → 100644
rf-blog/code/server/models/message.js
0 → 100644
1 | +import db from "../mongodb"; | ||
2 | +let messageSchema = db.Schema({ | ||
3 | + content: String, | ||
4 | + headerColor: { type: String, default: "#ff6c1a" }, | ||
5 | + nickname: { type: String, default: "匿名网友" }, | ||
6 | + createTime: String, | ||
7 | + likes: { type: Number, default: 0 }, | ||
8 | + comments: { type: Number, default: 0 }, | ||
9 | + replyList: [ | ||
10 | + { | ||
11 | + replyHeaderColor: { type: String, default: "#009688" }, | ||
12 | + replyContent: String, | ||
13 | + replyUser: { type: String, default: "匿名网友" }, | ||
14 | + byReplyUser: String, | ||
15 | + replyTime: String, | ||
16 | + }, | ||
17 | + ], | ||
18 | +}); | ||
19 | +export default db.model("message", messageSchema); |
rf-blog/code/server/models/user.js
0 → 100644
rf-blog/code/server/mongodb.js
0 → 100644
1 | +import mongoose from "mongoose"; | ||
2 | +import conf from "./config"; | ||
3 | +const DB_URL = `mongodb://${conf.mongodb.username}:${conf.mongodb.pwd}@${conf.mongodb.address}/${conf.mongodb.db}`; // 账号登陆 | ||
4 | +mongoose.Promise = global.Promise; | ||
5 | +mongoose.connect(DB_URL, { useMongoClient: true }, (err) => { | ||
6 | + if (err) { | ||
7 | + console.log("数据库连接失败!"); | ||
8 | + } else { | ||
9 | + console.log("数据库连接成功!"); | ||
10 | + } | ||
11 | +}); | ||
12 | +export default mongoose; |
rf-blog/code/server/router/index.js
0 → 100644
1 | +import koaRouter from "koa-router"; | ||
2 | +const router = koaRouter(); | ||
3 | + | ||
4 | +export default (app) => { | ||
5 | + /*----------------------admin-------------------------------*/ | ||
6 | + // 用户请求 | ||
7 | + router.post("/admin_api/user/login", app.admin.user.login); | ||
8 | + router.get("/admin_api/user/info", app.admin.user.info); | ||
9 | + router.get("/admin_api/user/list", app.admin.user.list); | ||
10 | + router.post("/admin_api/user/add", app.admin.user.add); | ||
11 | + router.post("/admin_api/user/update", app.admin.user.update); | ||
12 | + router.get("/admin_api/user/del", app.admin.user.del); | ||
13 | + | ||
14 | + // 文章请求 | ||
15 | + router.get("/admin_api/blog/list", app.admin.blog.list); | ||
16 | + router.post("/admin_api/blog/add", app.admin.blog.add); | ||
17 | + router.post("/admin_api/blog/update", app.admin.blog.update); | ||
18 | + router.get("/admin_api/blog/del", app.admin.blog.del); | ||
19 | + router.get("/admin_api/blog/info", app.admin.blog.info); | ||
20 | + | ||
21 | + // 标签请求 | ||
22 | + router.get("/admin_api/label/list", app.admin.label.list); | ||
23 | + router.post("/admin_api/label/add", app.admin.label.add); | ||
24 | + router.post("/admin_api/label/update", app.admin.label.update); | ||
25 | + router.get("/admin_api/label/del", app.admin.label.del); | ||
26 | + | ||
27 | + // 留言请求 | ||
28 | + router.get("/admin_api/message/list", app.admin.message.list); | ||
29 | + router.get("/admin_api/message/del", app.admin.message.del); | ||
30 | + router.post("/admin_api/message/delReply", app.admin.message.delReply); | ||
31 | + | ||
32 | + // 图片请求 | ||
33 | + router.post("/admin_api/uploadImage", app.admin.upload.uploadImage); | ||
34 | + router.post("/admin_api/delUploadImage", app.admin.upload.delUploadImage); | ||
35 | + | ||
36 | + /*----------------------client-------------------------------*/ | ||
37 | + // 文章请求 | ||
38 | + router.get("/client_api/blog/list", app.client.blog.list); | ||
39 | + router.get("/client_api/blog/info", app.client.blog.info); | ||
40 | + router.post("/client_api/blog/updateLikes", app.client.blog.updateLikes); | ||
41 | + router.post("/client_api/blog/updatePV", app.client.blog.updatePV); | ||
42 | + | ||
43 | + // 标签请求 | ||
44 | + router.get("/client_api/label/list", app.client.label.list); | ||
45 | + | ||
46 | + // 留言请求 | ||
47 | + router.post("/client_api/message/add", app.client.message.add); | ||
48 | + router.get("/client_api/message/list", app.client.message.list); | ||
49 | + router.get("/client_api/message/replyCount", app.client.message.replyCount); | ||
50 | + router.post( | ||
51 | + "/client_api/message/updateLikes", | ||
52 | + app.client.message.updateLikes | ||
53 | + ); | ||
54 | + router.post( | ||
55 | + "/client_api/message/updateReplys", | ||
56 | + app.client.message.updateReplys | ||
57 | + ); | ||
58 | + | ||
59 | + app.use(router.routes()).use(router.allowedMethods()); | ||
60 | +}; |
-
Please register or login to post a comment