Merge remote-tracking branch 'remotes/origin/dev-admin'
Showing
87 changed files
with
4912 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 | +} |
This diff is collapsed. Click to expand it.
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> |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
rf-blog/code/server/app.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/config.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/controller/admin/blog.js
0 → 100644
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
rf-blog/code/server/controller/admin/user.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/index.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/auth/index.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/func/db.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/func/file.js
0 → 100644
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/func/index.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/index.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/log/access.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/log/index.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/log/log.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/rule/index.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/middleware/send/index.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/models/blog.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/models/label.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/models/message.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/models/user.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/mongodb.js
0 → 100644
This diff is collapsed. Click to expand it.
rf-blog/code/server/router/index.js
0 → 100644
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment