前后端项目(前端)

这是图片

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import LoginView from './views/Login.vue'
import Layout from './views/Layout.vue'
</script>

<template>
<!--<LoginView />-->
<!-- <layout></layout>-->
<router-view></router-view>

</template>

<style scoped>

</style>

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
// import './assets/main.css'
import ElementPlus from 'element-plus'
import "element-plus/dist/index.css"
import { createApp } from 'vue'
import App from './App.vue'
import router from '@/router'
import {createPinia} from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import locale from 'element-plus/dist/locale/zh-cn.js'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
createApp(App).use(pinia).use(router).use(ElementPlus,{locale}).mount('#app')

views

Loyout.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<script setup>
import {
Management,
Promotion,
UserFilled,
User,
Crop,
EditPen,
SwitchButton,
CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
import {userInfoStore} from "@/stores/userInfo.js"
import {userInfoService} from "@/api/user.js"
import {ElMessage, ElMessageBox} from "element-plus"
import {useRouter} from "vue-router";
import {userTokenStore} from "@/stores/token.js"
const token = userTokenStore()
const router = useRouter()
const info = userInfoStore()
const getInfo=async ()=>{
let result = await userInfoService()
info.setUserInfoStore(result.data)
console.log(info.userInfoStore)
}
getInfo()
const command =(info)=>{
if(info==="logout"){
ElMessageBox.confirm(
'你确认要退出登录吗吗?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
token.removeToken()
userInfoStore().removeUserInfoStore()
router.push("/login")
ElMessage({
type: 'success',
message: '退出登录成功',
})
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消退出登录',
})
})
}else{
router.push('/user/'+info)
}
}
</script>

<template>
<el-container class="layout-container">
<!-- 左侧菜单 -->
<el-aside width="200px">
<div class="el-aside__logo"></div>
<el-menu active-text-color="#ffd04b" background-color="#232323" text-color="#fff"
router>
<el-menu-item index="/article/category">
<el-icon>
<Management />
</el-icon>
<span>文章分类</span>
</el-menu-item>
<el-menu-item index="/article/manage">
<el-icon>
<Promotion />
</el-icon>
<span>文章管理</span>
</el-menu-item>
<el-sub-menu >
<template #title>
<el-icon>
<UserFilled />
</el-icon>
<span>个人中心</span>
</template>
<el-menu-item index="/user/info">
<el-icon>
<User />
</el-icon>
<span>基本资料</span>
</el-menu-item>
<el-menu-item index="/user/avatar">
<el-icon>
<Crop />
</el-icon>
<span>更换头像</span>
</el-menu-item>
<el-menu-item index="/user/password">
<el-icon>
<EditPen />
</el-icon>
<span>重置密码</span>
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<!-- 右侧主区域 -->
<el-container>
<!-- 头部区域 -->
<el-header>
<div>黑马程序员:<strong>{{info.userInfoStore.nickname?info.userInfoStore.nickname:'未设置昵称'}}</strong></div>
<el-dropdown placement="bottom-end" @command="command">
<span class="el-dropdown__box">
<el-avatar :src="info.userInfoStore.userPic?info.userInfoStore.userPic:avatar" />
<el-icon>
<CaretBottom />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item>
<el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
<el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
<el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-header>
<!-- 中间区域 -->
<el-main>
<!-- <div style="width: 1290px; height: 570px;border: 1px solid red;">-->
<!-- -->
<!-- </div>-->
<RouterView></RouterView>
</el-main>
<!-- 底部区域 -->
<el-footer>大事件 ©2023 Created by 黑马程序员</el-footer>
</el-container>
</el-container>
</template>

<style lang="scss" scoped>
.layout-container {
height: 100vh;

.el-aside {
background-color: #232323;

&__logo {
height: 120px;
background: url('@/assets/logo.png') no-repeat center / 120px auto;
}

.el-menu {
border-right: none;
}
}

.el-header {
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;

.el-dropdown__box {
display: flex;
align-items: center;

.el-icon {
color: #999;
margin-left: 10px;
}

&:active,
&:focus {
outline: none;
}
}
}

.el-footer {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #666;
}
}
</style>

Login.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
import {userRegisterService} from "@/api/user.js";
import {userLoginService} from "@/api/user.js"
import {ElMessage} from "element-plus";
import{useRouter} from "vue-router";
import {userTokenStore} from "@/stores/token.js";
const userToken = userTokenStore()
const router = useRouter()
//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false)
const registerData = ref({
username:"",
password:"",
rePassword:""
})
const loginData=ref({
username:"",
password:""
})
const checkPassword =(rule,value,callback)=>{
if(value ===""){
callback(new Error("请填写重复密码"))
}else if(value!==registerData.value.password){
callback(new Error("请确保两次输入的密码一样"))
}else{
callback()
}
}
const rules = {
username:[
{required:true,message:"请输入用户名",trigger:"blur"},
{min:5,max:16,message:"请输入5到16位的用户名",trigger:"blur"}
],
password:[
{required:true,message:"请输入密码",trigger:"blur"},
{min:5,max:16,message: "请输入5到16位的密码",trigger:"blur"}
],
rePassword:[
{validator:checkPassword,trigger:"blur"}
]
}
const loginRules = {
username:[
{required:true,message:"请输入用户名",trigger:"blur"},
{min:5,max:16,message:"请输入5到16位的用户名",trigger:"blur"}
],
password:[
{required:true,message:"请输入密码",trigger:"blur"},
{min:5,max:16,message: "请输入5到16位的密码",trigger:"blur"}
],
}
const register= async ()=>{
let result =await userRegisterService(registerData.value)
// if (result.code === 0){
// alert(result.msg?result.msg:"注册成功")
// }else{
// alert("注册失败")
// }
console.log(result)
ElMessage.success(result.msg?result.msg:"注册成功")
}
const login = async ()=>{
let result =await userLoginService(loginData.value)
// if(result.code ===0){
// alert(result.msg?result.msg:"登录成功")
// }else{
// alert("登录失败")
// }
ElMessage.success(result.msg?result.msg:"登录成功")
userToken.setToken(result.data)
// 路由跳转
router.push("/")
}
</script>

<template>
<el-row class="login-page">
<el-col :span="12" class="bg"></el-col>
<el-col :span="6" :offset="3" class="form">
<!-- 注册表单 -->
<el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">
<el-form-item>
<h1>注册</h1>
</el-form-item>
<el-form-item prop="username">
<el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>
</el-form-item>
<el-form-item prop="rePassword">
<el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword"></el-input>
</el-form-item>
<!-- 注册按钮 -->
<el-form-item>
<el-button class="button" type="primary" auto-insert-space @click="register">
注册
</el-button>
</el-form-item>
<el-form-item class="flex">
<el-link type="info" :underline="false" @click="isRegister = false">
← 返回
</el-link>
</el-form-item>
</el-form>
<!-- 登录表单 -->
<el-form ref="form" size="large" autocomplete="off" v-else :model="loginData" :rules="loginRules">
<el-form-item>
<h1>登录</h1>
</el-form-item>
<el-form-item prop="username">
<el-input :prefix-icon="User" placeholder="请输入用户名" v-model="loginData.username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="loginData.password"></el-input>
</el-form-item>
<el-form-item class="flex">
<div class="flex">
<el-checkbox>记住我</el-checkbox>
<el-link type="primary" :underline="false">忘记密码?</el-link>
</div>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button>
</el-form-item>
<el-form-item class="flex">
<el-link type="info" :underline="false" @click="isRegister = true">
注册 →
</el-link>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>

<style lang="scss" scoped>
/* 样式 */
.login-page {
height: 100vh;
background-color: #fff;

.bg {
background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto,
url('@/assets/login_bg.jpg') no-repeat center / cover;
border-radius: 0 20px 20px 0;
}

.form {
display: flex;
flex-direction: column;
justify-content: center;
user-select: none;

.title {
margin: 0 auto;
}

.button {
width: 100%;
}

.flex {
width: 100%;
display: flex;
justify-content: space-between;
}
}
}
</style>

views/article

ArticleCategory.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
<script setup>
import {
Edit,
Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
import {getAllArticlesCategoryService,addArticleCategoryService,updateArticleCategoryService,deleteArticleCategoryService} from "@/api/article.js";
import {ElMessage,ElMessageBox} from "element-plus";
const categorys = ref([
{
"id": 3,
"categoryName": "美食",
"categoryAlias": "my",
"createTime": "2023-09-02 12:06:59",
"updateTime": "2023-09-02 12:06:59"
},
{
"id": 4,
"categoryName": "娱乐",
"categoryAlias": "yl",
"createTime": "2023-09-02 12:08:16",
"updateTime": "2023-09-02 12:08:16"
},
{
"id": 5,
"categoryName": "军事",
"categoryAlias": "js",
"createTime": "2023-09-02 12:08:33",
"updateTime": "2023-09-02 12:08:33"
}
])
const getAll = async () => {
let data = await getAllArticlesCategoryService()
categorys.value = data.data
}
getAll()

const dialogVisible =ref(false)
const categoryModel = ref({
categoryName:"",
categoryAlias:""
})
const rules ={
categoryName:{required:true,message:"请输入分类名称",trigger:"blur"},
categoryAlias:{required:true,message:"请输入分类别名",trigger:"blur"},
}
const addCategory = async () => {
let data = await addArticleCategoryService(categoryModel.value)
dialogVisible.value=false
ElMessage.success(data.msg?data.msg:"添加成功")
getAll()
}
const title = ref('')
const toUpdate = (row) => {
dialogVisible.value=true
title.value='修改分类'
categoryModel.value.categoryName=row.categoryName
categoryModel.value.categoryAlias=row.categoryAlias
categoryModel.value.id=row.id
}
const updateCategory = async () => {
let data = await updateArticleCategoryService(categoryModel.value)
dialogVisible.value=false
ElMessage.success(data.msg?data.msg:"修改成功")
getAll()
}
const clean = ()=>{
categoryModel.value.categoryName=''
categoryModel.value.categoryAlias=''
}
const deleteCategory = (row) => {
ElMessageBox.confirm(
'你确认删除该分类吗?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
let data=await deleteArticleCategoryService(row.id)
ElMessage({
type: 'success',
message: '删除成功',
})
getAll()
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
})
})
}
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header">
<span>文章分类</span>
<div class="extra">
<el-button type="primary" @click="dialogVisible=true,title='增加分类',clean()">添加分类</el-button>
</div>
</div>
</template>
<el-table :data="categorys" style="width: 100%">
<el-table-column label="序号" width="100" type="index"> </el-table-column>
<el-table-column label="分类名称" prop="categoryName"></el-table-column>
<el-table-column label="分类别名" prop="categoryAlias"></el-table-column>
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button :icon="Edit" circle plain type="primary" @click="toUpdate(row)"></el-button>
<el-button :icon="Delete" circle plain type="danger" @click="deleteCategory(row)"></el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
<!-- 添加分类弹窗 -->
<el-dialog v-model="dialogVisible" :title=title width="30%">
<el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
<el-form-item label="分类名称" prop="categoryName">
<el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
</el-form-item>
<el-form-item label="分类别名" prop="categoryAlias">
<el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="title == '增加分类'?addCategory():updateCategory()"> 确认 </el-button>
</span>
</template>
</el-dialog>
</el-card>
</template>

<style lang="scss" scoped>
.page-container {
min-height: 100%;
box-sizing: border-box;

.header {
display: flex;
align-items: center;
justify-content: space-between;
}
}
</style>




ArticleManage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
<script setup>
import {
Edit,
Delete
} from '@element-plus/icons-vue'
import {ElMessage, ElMessageBox} from "element-plus"
import {
getAllArticlesCategoryService,
getAllArticleService,
addArticleService,
updateArticleService,
deleteArticleService
} from "@/api/article.js"
import { ref } from 'vue'
import {userTokenStore} from '@/stores/token.js'
const tokenStore = userTokenStore().token
//文章分类数据模型
const categorys = ref([
{
"id": 3,
"categoryName": "美食",
"categoryAlias": "my",
"createTime": "2023-09-02 12:06:59",
"updateTime": "2023-09-02 12:06:59"
},
{
"id": 4,
"categoryName": "娱乐",
"categoryAlias": "yl",
"createTime": "2023-09-02 12:08:16",
"updateTime": "2023-09-02 12:08:16"
},
{
"id": 5,
"categoryName": "军事",
"categoryAlias": "js",
"createTime": "2023-09-02 12:08:33",
"updateTime": "2023-09-02 12:08:33"
}
])
const show = async ()=>{
let data =await getAllArticlesCategoryService()
categorys.value = data.data
}
show()
//用户搜索时选中的分类id
const categoryId=ref('')

//用户搜索时选中的发布状态
const state=ref('')

//文章列表数据模型
const articles = ref([
{
"id": 5,
"title": "陕西旅游攻略",
"content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
"coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
"state": "草稿",
"categoryId": 2,
"createTime": "2023-09-03 11:55:30",
"updateTime": "2023-09-03 11:55:30"
},
{
"id": 5,
"title": "陕西旅游攻略",
"content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
"coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
"state": "草稿",
"categoryId": 2,
"createTime": "2023-09-03 11:55:30",
"updateTime": "2023-09-03 11:55:30"
},
{
"id": 5,
"title": "陕西旅游攻略",
"content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
"coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
"state": "草稿",
"categoryId": 2,
"createTime": "2023-09-03 11:55:30",
"updateTime": "2023-09-03 11:55:30"
},
])

//分页条数据模型
const pageNum = ref(1)//当前页
const total = ref(20)//总条数
const pageSize = ref(5)//每页条数

//当每页条数发生了变化,调用此函数
const onSizeChange = (size) => {
pageSize.value = size
showMainInfo()
}
//当前页码发生变化,调用此函数
const onCurrentChange = (num) => {
pageNum.value = num
showMainInfo()
}
const showMainInfo =async ()=>{
let params ={
pageSize:pageSize.value,
pageNum:pageNum.value,
categoryId:categoryId.value?categoryId.value:null,
state:state.value?state.value:null
}
let show =await getAllArticleService(params)
articles.value=show.data.items
total.value=show.data.total
for(let i=0;i<categorys.value.length;i++){
let id =categorys.value[i].id
for(let j=0;j<articles.value.length;j++){
if (id === articles.value[j].categoryId){
articles.value[j].categoryName=categorys.value[i].categoryName

}
}
}
console.log(show.data.items)
}
showMainInfo()

import {Plus} from '@element-plus/icons-vue'
//控制抽屉是否显示
const visibleDrawer = ref(false)
//添加表单数据模型
const articleModel = ref({
title: '',
categoryId: '',
coverImg: '',
content:'',
state:''
})
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
const to_upload =(result) =>{
articleModel.value.coverImg = result.data
console.log(result)
}
const uploadArticle= async (state)=>{
articleModel.value.state = state
console.log(articleModel.value)
let result =await addArticleService(articleModel.value)
ElMessage.success("添加成功")
visibleDrawer.value = false
showMainInfo()
console.log(result)
}
const updateArticle = async (state)=>{
articleModel.value.state = state
let result = await updateArticleService(articleModel.value)
ElMessage.success("修改成功")
visibleDrawer.value = false
showMainInfo()
console.log(result)
}
const title = ref('')

const toUpdate = (row)=>{
title.value = "修改文章"
visibleDrawer.value=true
articleModel.value.title = row.title
articleModel.value.coverImg = row.coverImg
articleModel.value.categoryId = row.categoryId
articleModel.value.categoryName=row.categoryName
articleModel.value.content =row.content
articleModel.value.id = row.id
console.log(row)
}
const clean =()=>{
articleModel.value.title =''
articleModel.value.coverImg = ''
articleModel.value.categoryId = ''
articleModel.value.categoryName=''
articleModel.value.content ='<p></p>'
articleModel.value.state = ''
}
const deleteArticle=(row)=>{
ElMessageBox.confirm(
'你确认删除该分类吗?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
let result = await deleteArticleService(row.id)
ElMessage({
type: 'success',
message: '删除成功',
})
console.log(result)
showMainInfo()
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
})
})
}
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header">
<span>文章管理</span>
<div class="extra">
<el-button type="primary" @click="visibleDrawer=true,title='添加文章',clean()">添加文章</el-button>
</div>
</div>
</template>
<!-- 搜索表单 -->
<el-form inline>
<el-form-item label="文章分类:">
<el-select placeholder="请选择" v-model="categoryId" style="width: 180px">
<el-option
v-for="c in categorys"
:key="c.id"
:label="c.categoryName"
:value="c.id">
</el-option>
</el-select>
</el-form-item>

<el-form-item label="发布状态:">
<el-select placeholder="请选择" v-model="state" style="width: 180px">
<el-option label="已发布" value="已发布"></el-option>
<el-option label="草稿" value="草稿"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="showMainInfo">搜索</el-button>
<el-button @click="categoryId='';state=''">重置</el-button>
</el-form-item>
</el-form>
<!-- 文章列表 -->
<el-table :data="articles" style="width: 100%">
<el-table-column label="文章标题" width="400" prop="title"></el-table-column>
<el-table-column label="分类" prop="categoryName"></el-table-column>
<el-table-column label="发表时间" prop="createTime"> </el-table-column>
<el-table-column label="状态" prop="state"></el-table-column>
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button :icon="Edit" circle plain type="primary" @click="toUpdate(row)"></el-button>
<el-button :icon="Delete" circle plain type="danger" @click="deleteArticle(row)"></el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
<!-- 分页条 -->
<el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[3, 5 ,10, 15]"
layout="jumper, total, sizes, prev, pager, next" background :total="total" @size-change="onSizeChange"
@current-change="onCurrentChange" style="margin-top: 20px; justify-content: flex-end" />
<el-drawer v-model="visibleDrawer" :title="title" direction="rtl" size="50%">
<!-- 添加文章表单 -->
<el-form :model="articleModel" label-width="100px" >
<el-form-item label="文章标题" >
<el-input v-model="articleModel.title" placeholder="请输入标题"></el-input>
</el-form-item>
<el-form-item label="文章分类">
<el-select placeholder="请选择" v-model="articleModel.categoryId">
<el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="文章封面">
<!--
auto-upload:设置是否自动上传
action:设置服务器接口路径
name:设置上传的文件字段名
headers:设置上传的请求头
on-success:设置上传成功的回调函数

-->
<el-upload class="avatar-uploader" :auto-upload="true" action="/api/upload" name="multipartFile" :headers="{'Authorization':tokenStore}" :on-success="to_upload" :show-file-list="false">
<img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="文章内容">
<div class="editor">
<quill-editor
theme="snow"
v-model:content="articleModel.content"
contentType="html"
>
</quill-editor>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="title=='添加文章'?uploadArticle('已发布'):updateArticle('已发布')">发布</el-button>
<el-button type="info" @click="title=='添加文章'?uploadArticle('草稿'):updateArticle('草稿')">草稿</el-button>
</el-form-item>
</el-form>
</el-drawer>
</el-card>
</template>
<style lang="scss" scoped>
.page-container {
min-height: 100%;
box-sizing: border-box;

.header {
display: flex;
align-items: center;
justify-content: space-between;
}
}

.avatar-uploader {
:deep() {
.avatar {
width: 178px;
height: 178px;
display: block;
}

.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}

.el-upload:hover {
border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
}
}
.editor {
width: 100%;
:deep(.ql-editor) {
min-height: 200px;
}
}
</style>


views/user

UserAvatar.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<script setup>
import { Plus, Upload } from '@element-plus/icons-vue'
import {ref} from 'vue'
import avatar from '@/assets/default.png'
import {userInfoStore} from "@/stores/userInfo.js"
import {userTokenStore} from '@/stores/token.js'
import {updateUserAvatarService} from "@/api/user.js"
import {ElMessage} from "element-plus"
const token = userTokenStore().token
const user = userInfoStore()
const uploadRef = ref()

//用户头像地址
const imgUrl= ref(user.userInfoStore.userPic)
const to_upload = (result)=>{
imgUrl.value=result.data
}
const upload =async()=> {
let result =await updateUserAvatarService(imgUrl.value)
user.userInfoStore.userPic=imgUrl.value
ElMessage.success("修改成功")
}
</script>

<template>
<el-card class="page-container">
<template #header>
<div class="header">
<span>更换头像</span>
</div>
</template>
<el-row>
<el-col :span="12">
<el-upload
ref="uploadRef"
class="avatar-uploader"
:show-file-list="false"
action="/api/upload"
name="multipartFile"
:headers="{'Authorization':token}"
:auto-upload="true"
:on-success="to_upload"
>
<img v-if="imgUrl" :src="imgUrl" class="avatar" />
<img v-else :src="avatar" width="278" />
</el-upload>
<br />
<el-button type="primary" :icon="Plus" size="large" @click="uploadRef.$el.querySelector('input').click()">
选择图片
</el-button>
<el-button type="success" :icon="Upload" size="large" @click="upload">
上传头像
</el-button>
</el-col>
</el-row>
</el-card>
</template>

<style lang="scss" scoped>
.avatar-uploader {
:deep() {
.avatar {
width: 278px;
height: 278px;
display: block;
}

.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}

.el-upload:hover {
border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 278px;
height: 278px;
text-align: center;
}
}
}
</style>

UserInfo.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<script setup>
import { ref } from 'vue'
import {userInfoStore} from "@/stores/userInfo.js"
import {updateUserService} from "@/api/user.js"
import {ElMessage} from "element-plus"
const user = userInfoStore()
const userInfo = ref({...user.userInfoStore})


const rules = {
nickname: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' },
{
pattern: /^\S{2,10}$/,
message: '昵称必须是2-10位的非空字符串',
trigger: 'blur'
}
],
email: [
{ required: true, message: '请输入用户邮箱', trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
]
}
const updateDate=async ()=>{
let result = await updateUserService(userInfo.value)
ElMessage.success("修改成功")
user.userInfoStore.nickname=userInfo.value.nickname
user.userInfoStore.email = userInfo.value.email
console.log(result)
}
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header">
<span>基本资料</span>
</div>
</template>
<el-row>
<el-col :span="12">
<el-form :model="userInfo" :rules="rules" label-width="100px" size="large">
<el-form-item label="登录名称">
<el-input v-model="userInfo.username" disabled></el-input>
</el-form-item>
<el-form-item label="用户昵称" prop="nickname">
<el-input v-model="userInfo.nickname"></el-input>
</el-form-item>
<el-form-item label="用户邮箱" prop="email">
<el-input v-model="userInfo.email"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="updateDate">提交修改</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-card>
</template>

UserResetPassword.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<script setup>
import { ref } from 'vue'
import {ElMessage, ElMessageBox} from "element-plus"
import {updatePwdService} from "@/api/user.js"
import {userTokenStore} from "@/stores/token.js"
import {userInfoStore} from "@/stores/userInfo.js"
import {useRouter} from "vue-router";
const router = useRouter()
const token = userTokenStore()
const user = userInfoStore()
const userInfo = ref({
old_pwd: '',
new_pwd: '',
re_pwd: '',
})
const rules = {
old_pwd: [
{ required: true, message: '请输入旧密码', trigger: 'blur' },
{min:5,max:16,message: "请输入5到16位的密码",trigger:"blur"}
],
new_pwd: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{min:5,max:16,message: "请输入5到16位的密码",trigger:"blur"}
],
re_pwd:[
{ required: true, message: '请重复输入新密码', trigger: 'blur' },
{min:5,max:16,message: "请输入5到16位的密码",trigger:"blur"}
]
}
const to_pwd =()=>{
ElMessageBox.confirm(
'你确认修改密码吗?',
'温馨提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
let result = await updatePwdService(userInfo.value)
user.removeUserInfoStore()
token.removeToken()
router.push("/login")
ElMessage({
type: 'success',
message: '修改成功',
})
console.log(result)

})
.catch(() => {
ElMessage({
type: 'info',
message: '取消修改',
})
})
}
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header">
<span>重置密码</span>
</div>
</template>
<el-row>
<el-col :span="12">
<el-form :model="userInfo" :rules="rules" label-width="100px" size="large">
<el-form-item label="旧密码" prop="old_pwd">
<el-input v-model="userInfo.old_pwd"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="new_pwd">
<el-input v-model="userInfo.new_pwd"></el-input>
</el-form-item>
<el-form-item label="重复新密码" prop="re_pwd">
<el-input v-model="userInfo.re_pwd"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="to_pwd">修改密码</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-card>
</template>

utils

request.js 拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import axios from "axios";
import {ElMessage} from "element-plus";
import {userTokenStore} from "@/stores/token.js";
import router from '@/router'
// import{useRouter} from "vue-router";
// const baseURL = "http://localhost:8080"
const baseURL = "/api"
const instance= axios.create({baseURL})

instance.interceptors.request.use(
config =>{
let tokenStore = userTokenStore()
if(tokenStore.token){
config.headers.Authorization = tokenStore.token
}
return config
},
error => {
alert("token缺失")
return Promise.reject(error)
}
)

instance.interceptors.response.use(
response =>{
if(response.data.code === 0 ){
return response.data
}
ElMessage.error(response.data.message?response.data.message:"服务异常")
return Promise.reject(response.data)


} ,
error => {
if(error.response.status === 401){
ElMessage.error("未登录")
router.push("/login")
}else{
ElMessage.error("服务异常")
}
return Promise.reject(error)//异步的状态转换成失败的状态
}
)
export default instance;


api

article.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import request from "@/utils/request.js";
import {userTokenStore} from "@/stores/token.js";

export const getAllArticlesCategoryService = () => {
const token = userTokenStore().token
console.log(token)
// return request.get("/category",{headers:{"Authorization":token}});
return request.get("/category");
}
export const addArticleCategoryService = (categoryData) =>{
return request.post("/category",categoryData);
}
export const updateArticleCategoryService = (categoryData) =>{
return request.put("/category",categoryData);
}

export const deleteArticleCategoryService = (categoryId) =>{
return request.delete("/category?id="+categoryId);
}
export const getAllArticleService = (articleData) =>{
return request.get('/article',{params:articleData})
}

export const addArticleService = (articleData) =>{
return request.post("/article",articleData);
}

export const updateArticleService = (articleData) =>{
return request.put("/article",articleData);
}

export const deleteArticleService = (articleId)=>{
return request.delete("/article?id="+articleId);
}

user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

import request from "@/utils/request.js"
export const userRegisterService = (registerData) =>{
const params = new URLSearchParams()
for (let key in registerData) {
params.append(key, registerData[key])
}
return request.post("/user/register",params);
}
export const userLoginService = (loginData) =>{
const params = new URLSearchParams()
for (let key in loginData) {
params.append(key, loginData[key])
}
return request.post("/user/login",params);
}

export const userInfoService = () =>{
return request.get('/user/userInfo')
}

export const updateUserService = (userData) =>{
return request.put("/user/update",userData);
}
export const updateUserAvatarService = (userData) =>{
return request.patch("/user/updateAvatar?avatarUrl="+userData);
}
export const updatePwdService = (userData) =>{
return request.patch("/user/updatePwd",userData)
}

router

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import {createRouter,createWebHashHistory} from "vue-router";
import Layout from "@/views/Layout.vue";
import Login from "@/views/Login.vue";
import ArticleCategory from "@/views/article/ArticleCategory.vue";
import ArticleManage from "@/views/article/ArticleManage.vue";
import UserInfo from "@/views/user/UserInfo.vue";
import UserResetPassword from "@/views/user/UserResetPassword.vue";
import UserAvatar from "@/views/user/UserAvatar.vue";
const routes = [

{
path:"/",
component:Layout,
redirect:"/article/manage",
children:[
{path:"/article/category",component:ArticleCategory},
{path:"/article/manage",component:ArticleManage},
{path:"/user/info",component:UserInfo},
{path:"/user/avatar",component:UserAvatar},
{path:"/user/password",component:UserResetPassword},
]
},
{path:"/login",component:()=>import("@/views/Login.vue"),},
]
const router=createRouter({
history:createWebHashHistory(),
routes:routes
});
export default router

stores (pinia)

token.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

import {defineStore} from "pinia";
import {ref} from "vue";

export const userTokenStore = defineStore("token",()=>{
const token = ref('')
const setToken = (newToken)=>{
token.value=newToken;
}
const removeToken = ()=>{
token.value = ''
}
return{
token,setToken,removeToken
}
},
{persist:true}
)

userInfo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {defineStore} from "pinia";
import {ref} from "vue";
export const userInfoStore = defineStore("userInfoStore", ()=>{
const userInfoStore = ref({})
const setUserInfoStore = (newInfo)=>{
userInfoStore.value = newInfo
}
const removeUserInfoStore = ()=>{
userInfoStore.value = {}
}
return {
userInfoStore,setUserInfoStore,removeUserInfoStore
}
},{
persist:true
})



vite.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server:{
proxy:{
'/api':{
target: 'http://localhost:8080',
changeOrigin: true,
rewrite:(path)=>path.replace(/^\/api/,'')
}
}
}
})