後臺管理頁面,需要配合NODE.JS搭建的EXPRESS服務器使用。
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import {
Button,
Input,
Form,
Link,
Divider,
Upload,
Dialog,
Card,
Popover,
MessageBox,
Message,
Loading,
Breadcrumb,
BreadcrumbItem,
Select,
Option,
Table,
TableColumn,
Avatar,
Pagination,
Checkbox,
CheckboxGroup,
} from 'element-ui';
// 局部引入必須這麼做才能正常使用
Vue.prototype.$message = Message
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$loading = Loading.service
Vue.use(Button)
Vue.use(Input)
Vue.use(Form)
Vue.use(Link)
Vue.use(Divider)
Vue.use(Upload)
Vue.use(Dialog)
Vue.use(Card)
Vue.use(Popover)
Vue.use(Breadcrumb)
Vue.use(BreadcrumbItem)
Vue.use(Select)
Vue.use(Option)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Avatar)
Vue.use(Pagination)
Vue.use(Checkbox)
Vue.use(CheckboxGroup)
Vue.config.productionTip = false
Vue.prototype.$url_posts = "http://localhost:8081/posts"
Vue.prototype.$url_categories = "http://localhost:8081/categories"
new Vue({
render: h => h(App),
router,
}).$mount('#app')
這段代碼主要執行了以下操作:
-
導入所需的庫和組件:
- 導入 Vue 核心庫。
- 導入根組件 App.vue。
- 導入路由配置文件。
- 從 Element UI 庫中導入了一系列 UI 組件,如 Button、Input、Form 等。
-
配置 Vue 全局屬性:
- 將 Message、MessageBox 和 Loading 服務掛載到 Vue 原型對象上,這樣可以在整個應用中直接使用它們,例如
this.$message
、this.$confirm
和this.$loading
。
- 將 Message、MessageBox 和 Loading 服務掛載到 Vue 原型對象上,這樣可以在整個應用中直接使用它們,例如
-
註冊 UI 組件:
- 使用 Vue.use() 方法註冊導入的 Element UI 組件,使其可以在 Vue 應用中全局使用。
-
關閉 Vue 生產環境的提示信息:
- 設置
Vue.config.productionTip = false
,以關閉生產環境下的提示信息。
- 設置
-
定義全局 API 地址:
- 在 Vue 原型對象上定義了
$url_posts
和$url_categories
兩個屬性,分別用於存儲 post 和 category 相關 API 的 URL。這樣在整個應用中都可以方便地訪問這兩個 URL。
- 在 Vue 原型對象上定義了
-
創建 Vue 實例並掛載:
- 創建一個新的 Vue 實例,指定渲染函數以將根組件 App.vue 渲染到頁面上,並傳入路由配置。最後將 Vue 實例掛載到頁面的 '#app' 元素上。
簡言之,這段代碼的主要作用是:
- 導入所需的庫和組件。
- 配置 Vue 全局屬性和 UI 組件。
- 定義全局 API 地址。
- 創建 Vue 實例並將其掛載到頁面上。
App.vue
<template>
<div id="app">
<header>
<div class="header-left">
<ul>
<li
:class="{ active: activeRoute === 'Management' }"
class="nav-btn clickable"
@click="jump2('Management')"
>管理
</li>
<li
:class="{ active: activeRoute === 'Edit' }"
class="nav-btn clickable"
@click="jump2('Edit')"
>新隨筆
</li>
<li
:class="{ active: activeRoute === 'Edit2' }"
class="nav-btn auto"
>編輯
</li>
<li
:class="{ active: activeRoute === 'Category' }"
class="nav-btn clickable"
@click="jump2('Category')"
>分類
</li>
</ul>
</div>
<div class="header-right">
<ul>
<li>
<div id="user" slot="reference">
<el-avatar
icon="el-icon-user-solid"
style="display: inline-block"
></el-avatar>
</div>
</li>
</ul>
</div>
</header>
<main>
<router-view></router-view>
</main>
</div>
</template>
<script>
export default {
name: "App",
computed: {
activeRoute() {
return this.$route.name
}
},
methods: {
jump2(router_name) {
if (this.activeRoute !== router_name) {
this.$router.push({ name: router_name });
}
},
switch2management() {
// 棄用,待修改
if (this.activeRoute !== 'Management') {
if (this.activeRoute === 'Edit2') {
this.$confirm('跳轉頁面將丟失未保存的修改內容,確認跳轉?', '提示', {
confirmButtonText: '確定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
this.$router.push({ name: "Management" });
})
} else {
this.$router.push({ name: "Management" });
}
}
},
},
};
</script>
<style>
/* 全局樣式在這裏設置,其它一律scoped */
body {
margin: 0;
}
#app {
/* font-family: Avenir, Helvetica, Arial, sans-serif; */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* text-align: center; */
/* color: #2c3e50; */
/* margin-top: 60px; */
}
</style>
<style scoped>
#app {
background: white;
/* 設定最大寬度,並且居中 */
max-width: 1380px;
min-width: 450px;
margin: 0 auto;
padding: 0 15px;
/* 用下面的grid配置在打開有element表格的頁面有一個神奇的無限往右延長的BUG */
/* display: grid;
grid-template: 65px 1fr / 25px 1fr 25px; */
display: flex;
flex-direction: column;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微軟雅黑",Arial,sans-serif;
}
header {
/* grid-column: 2 / 3; */
margin: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
/* 下面這個線畫到在main上畫 */
/* border-bottom: 1px solid #dcdfe6; */
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li {
display: inline-block;
box-sizing: border-box;
/* li之間互相應該有一定間隙 */
margin: 5px;
user-select: none;
}
.nav-btn {
/* 按鈕靜默狀態樣式 */
/* 上下10px 左右22px */
padding: 10px 22px;
/* 字體影響非常非常大................ */
font-size: 18px;
font-family: 'Courier New', Courier, monospace;
/* 特效之靜默狀態 */
opacity: 0.5;
border: 1.3px solid #292b2d56;
border-radius: 5%;
}
.nav-btn.clickable {
cursor: pointer;
/* 保持與element一致 */
color: #409EFF;
transition: 0.3s ease-in-out;
}
.nav-btn.clickable:hover, .nav-btn.clickable.active {
/* 鼠標懸浮時亮起,0字體改變,1是變成不透明,2是邊框顏色改變,4懸浮 */
color: #409EFF;
opacity: 1;
border: 1.3px solid #409EFF;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.5);
transition: 0.3s ease-in-out;
}
.nav-btn.auto {
color: #67C23A;
}
.nav-btn.auto.active {
color: #67C23A;
opacity: 1;
border: 1.3px solid #67C23A;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.5);
transition: 0.3s ease-in-out;
}
div#user {
cursor: pointer;
}
main {
border-top: 1px solid #dcdfe6;
}
</style>
這段代碼是一個Vue.js的單文件組件,包括了一個template、一個script和兩個style塊。這個組件表示了一個頁面的佈局和導航,其中包括一個頭部和一個主體。頭部包括了一個左側的導航欄和一個右側的用戶頭像。左側導航欄包括了四個導航按鈕,分別是“管理”、“新隨筆”、“編輯”和“分類”。主體部分是一個router-view組件,用於顯示路由對應的內容。
在script部分,該組件的名字是“App”,定義了兩個方法:jump2和switch2management,以及一個computed屬性activeRoute。activeRoute返回當前路由的名稱,jump2方法根據傳入的路由名稱進行跳轉,而switch2management方法目前被棄用了。
在style部分,第一個style塊是全局樣式的設置,第二個style塊是局部樣式,它們都是通過scoped屬性限定了作用域。該組件的整體樣式是一個白色背景,最大寬度爲1380像素,居中顯示。頭部採用了flex佈局,左側導航欄使用了inline-block佈局,右側用戶頭像使用了slot插槽。導航按鈕有靜默狀態和懸浮狀態,點擊後會有對應的路由跳轉。主體部分有一個上邊框,用於分隔頭部和主體。
vue-my-cnblog\src\router\index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Management from '@/page/Management.vue'
import Edit from '@/page/Edit.vue'
import Edit2 from '@/page/Edit2.vue'
import PostInfo from '@/page/PostInfo.vue'
import Category from '@/page/Category.vue'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
name: 'Management',
component: Management,
},
{
path: '/edit',
name: 'Edit',
component: Edit,
// 這個新隨筆頁面,用戶可以主動進入
},
{
path: '/edit2',
name: 'Edit2',
component: Edit2,
// 真 · 編輯頁面,用戶不能主動進入
},
{
path: '/postinfo',
name: 'PostInfo',
component: PostInfo,
},
{
path: '/category',
name: 'Category',
component: Category,
},
],
})
export default router
這段代碼使用了 Vue.js 和 Vue Router 插件,定義了一個路由器實例 router,並指定了多個路由對象作爲它的配置項,每個路由對象包含了路徑、名稱和組件。其中:
'/'
表示默認路徑,對應的組件是 Management.vue。'/edit'
路徑表示進入編輯頁面,對應的組件是 Edit.vue。'/edit2'
路徑表示真正的編輯頁面,對應的組件是 Edit2.vue。'/postinfo'
路徑表示文章信息頁面,對應的組件是 PostInfo.vue。'/category'
路徑表示文章分類頁面,對應的組件是 Category.vue。
路由器實例通過 Vue.use(VueRouter) 進行初始化,並使用 new VueRouter(options) 來創建,其中 options 包含了多個路由配置。這個路由器實例最終會被導出並在其他模塊中使用。
需要注意的是,Edit2 路徑對應的組件是不可以直接通過 URL 訪問的,只能在代碼中被調用。
Management.vue
<template>
<div id="management">
<main>
<table>
<thead>
<tr>
<th>標題</th>
<th>發佈時間</th>
<th>發佈狀態</th>
<th>操作1</th>
<th>操作2</th>
</tr>
</thead>
<tbody>
<tr v-for="post in posts" :key="post.id">
<td><div class="post_title" @click="postInfo(post)">{{ post.title }}</div></td>
<td>{{ post.pubDate }}</td>
<td>{{ post.state }}</td>
<td><div class="btn" @click="editPost(post)">編輯</div></td>
<td><div class="btn" @click="removePost(post)">刪除</div></td>
</tr>
</tbody>
</table>
</main>
<footer>
<el-pagination
background
layout="prev, pager, next"
:current-page.sync="currentPage"
:page-size="5"
:total="total">
</el-pagination>
</footer>
</div>
</template>
<script>
import axios from 'axios'
// import qs from 'qs'
const PAGE_SIZE = 5
export default {
name: 'Management',
data() {
return {
users: [],
title: '',
content: '',
currentPage: 1,
allPosts: [],
}
},
computed: {
total() {
return this.allPosts.length;
},
start() {
return (this.currentPage - 1) * PAGE_SIZE
},
end() {
return this.currentPage * PAGE_SIZE
},
posts() {
// 變化,例如說,刪除一條,那麼allposts變化導致,total和posts變化
return this.allPosts.slice(this.start, this.end)
}
},
created() {
console.log('Management created')
this.reloadPosts()
},
methods: {
reloadPosts() {
axios.get(this.$url_posts)
.then(resp => {
// console.log(resp.data)
// 接受到數據後進行格式化,每當數據更新應該調用該方法
if (resp.data) {
this.allPosts = resp.data.map(post => {
return {
id: post.id,
title: post.title,
createTime: new Date(post.createTime).toLocaleString(),
category: [],
state: post.state,
pubDate: new Date(post.pubDate).toLocaleString(),
}
})
}
})
.catch(err => {
console.log(err)
})
},
postInfo(post) {
const POST_ID = post.id
if (this.$route.name !== 'PostInfo') {
this.$router.push({
name: "PostInfo",
query: {
post_id: POST_ID,
},
});
}
},
removePost(post) {
const POST_ID = post.id
console.log('removePost: ' + POST_ID)
// 確認一下
this.$confirm('此操作將永久刪除該文件, 是否繼續?', '提示', {
confirmButtonText: '確定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
// 把屏幕鎖了防止亂點
const LOADING = this.$loading({
lock: true,
text: '正在刪除',
// spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
// 發送刪除文件的請求
axios.delete(this.$url_posts + `/${POST_ID}`)
.then(() => {
setTimeout(() => {
// 刷新
this.reloadPosts()
// 至少鎖1秒才解除
LOADING.close();
this.$message({
type: 'success',
message: '刪除成功!',
});
}, 1000);
})
.catch(err => {
console.log(err)
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消刪除'
});
});
},
editPost(post) {
const POST_ID = post.id
// 跳轉到對應的文件詳情頁
if (this.$route.name !== 'Edit2') {
this.$router.push({
name: "Edit2",
query: {
post_id: POST_ID,
},
});
}
},
editContent(id) {
axios.get(`http://localhost:8081/users/${id}`)
.then(resp => {
this.content = resp.data
})
.catch(err => {
console.log(err)
})
},
},
}
</script>
<style scoped>
table {
width: 100%;
table-layout: fixed;
/* fixed之後默認平均分配位置,不會按內容分配 */
border-collapse: collapse;
/* collapse只有設置border1時會把那個空餘的地方變成線,其它時候好像沒啥用 */
}
thead, tbody {
/* font-size: 14px; */
font-family:'Courier New', Courier, monospace;
text-align: center;
}
th, td {
padding: 10px;
/* 看上去寬鬆點 */
border: 1px solid black;
}
th {
letter-spacing: 2px;
/* 和td區分一下 */
}
thead th:nth-child(1) {
width: 50%;
}
div.post_title {
height: 50px;
overflow-y: hidden;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
user-select: none;
}
div.post_title:hover {
overflow-y: visible;
color: #409EFF;
text-decoration: underline;
}
div.btn {
cursor: pointer;
border: 1px solid black;
/* 調整按鈕大小 */
padding: 5px;
border-radius: 10px;
}
div.btn:hover {
color: #409EFF;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
footer {
height: 50px;
display: flex;
justify-content: center;
align-items: center;
}
</style>
這段代碼是一個Vue.js組件,包括一個HTML模板和一個JavaScript腳本以及一個局部的CSS樣式。
該組件創建了一個管理界面,包括一個帶有標題的表格和一個分頁組件。表格包含了標題、發佈時間、發佈狀態以及兩個操作按鈕。分頁組件用於分頁顯示所有的文章。
數據部分包括一個名爲“allPosts”的數組,其中存儲了所有文章的信息。在組件創建後,它使用Axios庫從服務器中獲取文章列表,並將其格式化爲所需的格式。在點擊“編輯”或“刪除”按鈕時,將使用Axios庫向服務器發送請求來執行相應的操作。
HTML模板包含一個帶有唯一ID“management”的div元素,其中包含一個主區域和一個頁腳區域。主區域包含一個帶有標題的表格,表格頭包含標題、發佈時間、發佈狀態和兩個操作按鈕。表格主體使用Vue的v-for指令將文章列表中的每一篇文章都渲染爲一個表格行。頁腳區域包含一個分頁組件,可以用於分頁顯示所有文章。
JavaScript腳本定義了一個Vue.js組件,名稱爲“Management”。它包含了一些數據屬性、計算屬性和方法。其中數據屬性包括一個名爲“users”的空數組、一個名爲“title”的空字符串、一個名爲“content”的空字符串、一個名爲“currentPage”的數字1和一個名爲“allPosts”的空數組。計算屬性包括一個名爲“total”的方法,該方法返回文章列表的總數;一個名爲“start”的方法,該方法計算當前頁的起始索引;一個名爲“end”的方法,該方法計算當前頁的結束索引;一個名爲“posts”的方法,該方法使用數組切片從“allPosts”中提取當前頁的文章列表。組件生命週期鉤子函數中的“created”鉤子調用了“reloadPosts”方法,該方法使用Axios庫從服務器中獲取文章列表並將其格式化。
方法包括“reloadPosts”方法,該方法使用Axios庫從服務器中獲取文章列表,並將其格式化爲所需的格式。在點擊“編輯”或“刪除”按鈕時,將使用Axios庫向服務器發送請求來執行相應的操作。其他方法包括“postInfo”方法,該方法在單擊文章標題時用於打開文章詳細信息頁面;“removePost”方法,該方法在單擊“刪除”按鈕時用於刪除文章;“editPost”方法,該方法在單擊“編輯”按鈕時用於打開文章編輯頁面;以及“editContent”方法,該方法使用Axios庫從服務器中獲取文章內容。
CSS樣式定義了一些用於佈局和樣式化表格、標題、按鈕和分頁組件的樣式規則。其中一些規則使用了Vue.js的“scoped”屬性,以確保它們只應用於該組件的元素。