首页 layout 架子
登录访问拦截(router/index.js)
只有登录页,可以未授权的时候访问,其他所有页面,都需要先登录再访问
// 登录访问拦截 => 默认是直接放行的
// 根据返回值决定,是放行还是拦截
// 返回值:
// 1. undefined / true 直接放行
// 2. false 拦回from的地址页面
// 3. 具体路径 或 路径对象 拦截到对应的地址
// '/login' { name: 'login' }
router.beforeEach((to) => {
// 如果没有token, 且访问的是非登录页,拦截到登录,其他情况正常放行
const useStore = useUserStore()
if(!useStore.token && to.path !== '/login') return '/login'
})
获取用户信息
1.封装接口(api/user.js)
//获取用户基本信息接口get
export const userGetInfoService = () => request.get('/my/userinfo')
2.stores/modules/user.js 定义数据
const user = ref({})
//一调方法,就发请求,获取数据
const getUser = async () => {
//调用刚刚封装的接口,请求获取数据
const res = await userGetInfoService()
user.value = res.data.data
}
return { token, setToken, removeToken, user, getUser }
},
3.layout/LayoutContainer
页面中调用
import { onMounted } from 'vue'
const userStore = useUserStore()
onMounted(() => {
userStore.getUser()
})
页面请求成功
4.动态渲染
<div>
黑马程序员:<strong>{{ userStore.user.nickname || userStore.user.username }}</strong>
</div>
<el-avatar :src="userStore.user.user_pic || avatar" />
退出功能
1.注册点击事件
通过@command监听菜单选择,每个菜单栏里都有一个·command标识,点击某一个选项之后会有一个形参,基于形参做判断,logout就退出,其他的就路由跳转
<el-dropdown placement="bottom-end" @command="onCommand">
<el-dropdown-menu>
<el-dropdown-item command="profile" :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>
2.添加退出功能
import { useUserStore } from '@/stores'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()
const handleCommand = async (key) => {
if (key === 'logout') {
//退出操作
await ElMessageBox.confirm('Are you sure', '温馨提示', {
type: 'warning',
confirmButtonText: '确认',
cancelButtonText: '取消'
})
//清除本地的数据(token + user信息)
userStore.removeToken()
userStore.setUser({})
router.push('/login')
} else {
router.push('/user/${key}')
}
}
文章分类和文章管理的架子搭建
1.基本结构样式,用到了 el-card 组件
<template>
<el-card class="page-container">
<template #header>
<div class="header">
<span>文章分类</span>
<div class="extra">
<el-button type="primary">添加分类</el-button>
</div>
</div>
</template>
...
</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>
2.因为多个页面复用,封装成组件
-
props 定制标题
-
默认插槽 default 定制内容主体
-
具名插槽 extra 定制头部右侧额外的按钮
-
使用时直接引入并且更改需要改的内容即可
<script setup> //父传子 //在vue3的setup中,我们使用defineProps来定义父组件传递的props //通过使用defineProps,我们可以明确地声明组件所接受的props defineProps({ title: { required: true, type: String } }) </script> <template> <el-card class="page-container"> <template #header> <div class="header"> <!--定制标题,父传子--> <span>{{ title }}</span> <div class="extra"> <!--具名插槽,定制按钮--> <slot name="extra"></slot> </div> </div> </template> <slot></slot> </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>
3.页面中直接使用测试 ( unplugin-vue-components 会自动注册)
文章分类测试代码:
<script setup></script>
<template>
<page-container title="添加分类">
<template #extra>
<el-button>测试按钮</el-button>
</template>
主体部分,是表格 + del
</page-container>
</template>
<style lang="scss" scoped></style>
文章管理测试代码:
<script setup></script>
<template>
<page-container title="文章分类">
<template #extra>
<el-button>测试按钮</el-button>
</template>
主体部分,是表格
</page-container>
</template>
<style lang="scss" scoped></style>
文章分类渲染
1.新建 api/article.js
封装获取频道列表的接口get
//获取文章分类表格数据
//封装接口
import request from '@/utils/request'
export const artgetChannelsService = () => request.get('/my/userinfo', {})
2.页面中调用接口,获取数据存储(ArticleChannel.vue)
import { artgetChannelsService } from '../../api/article'
const channelList = ref([])
const getChannelList = async () => {
const res = await artgetChannelsService()
channelList.value = res.data.data
console.log(channelList.value)
}
//一进页面就调用
getChannelList()
el-table 表格动态渲染
定义了channelList,用来接收表格的数据;定义loading变量,用来控制加载动态的实现;getChannelList()用来发请求; onEditChannel()和 onDelChannel()是执行编辑和删除按钮逻辑的函数
<script setup>
import { ref } from 'vue'
import { Edit, Delete } from '@element-plus/icons-vue'
import { artgetChannelsService } from '../../api/article'
const channelList = ref([])
const loading = ref(false)
const onEditChannel = (row, $index) => {
console.log(row, $index)
}
const onDelChannel = (row, $index) => {
console.log(row, $index)
}
const getChannelList = async () => {
//发送请求时开启
loading.value = true
const res = await artgetChannelsService()
channelList.value = res.data.data
//请求结束后开启
loading.value = false
console.log(channelList.value)
}
//一进页面就调用
getChannelList()
</script>
<el-table v-loading="loading" :data="channelList" style="width: 100%">
<el-table-column type="index" label="序号" width="100"></el-table-column>
<el-table-column label="分类名称" prop="cate_name"></el-table-column>
<el-table-column label="分类别名" prop="cate_alias"></el-table-column>
<el-table-column label="操作" width="150">
<!--自定义按钮 默认插槽-->
<!--row就是channelList的一项,$index下标-->
<!--从obj中解构出row 当前行 和 $index 当前行下标-->
<template #default="{ row, $index }">
<el-button
:icon="Edit"
circle
plain
type="primary"
@click="onEditChannel(row, $index)"
></el-button>
<el-button
:icon="Delete"
circle
plain
type="danger"
@click="onDelChannel(row, $index)"
></el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="无"></el-empty>
</template>
</el-table>
</page-container>
</template>
封装弹层组件 ChannelEdit
添加 和 编辑,可以共用一个弹层,所以可以将弹层封装成一个组件,在article下新建components文件夹,新建 ChannelEdit.vue以封装弹层。
组件对外暴露一个方法 open, 基于 open 的参数,初始化表单数据,并判断区分是添加 还是 编辑
-
open({ }) => 添加操作,添加表单初始化无数据
-
open({ id: xx, ... }) => 编辑操作,编辑表单
-
初始化需回显open调用后,可以打开弹窗
-
v-model="dialogVisible": v-model与 dialogVisible绑定。
-
const dialogVisible = ref(false) 设置dialogVisible变量,默认为false
-
const open = (row) => {dialogVisible.value = true} 设置open方法,控制弹框出现消失和接收参数
-
向外暴露,方便父组件调用方法:
defineExpose({ open})
<script setup>
import { ref } from 'vue'
const dialogVisible = ref(false)
//组件对外暴露一个方法open,基于open传来的参数,区分是添加还是编辑
//open({}) => 表单没有渲染,说明是添加
//open({id,cate_name,...}) => 表单需要渲染,说明是编辑
//open调用后,可以打开弹窗
const open = (row) => {
dialogVisible.value = true
// formModel.value = { ...row }
}
//向外暴露
defineExpose({
open
})
</script>
<template>
<el-dialog
v-model="dialogVisible"
title="add popup"
width="500"
:before-close="handleClose"
>
<div>comtent</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
8.在父组件中调用: <channel-edit ref="dialog"></channel-edit>,通过ref与 dialog变量绑定
<template #empty>
<el-empty description="无"></el-empty>
</template>
</el-table>
<channel-edit ref="dialog"></channel-edit>
</page-container>
</template>
9.点击调用方法显示弹窗(基于传参,判断是添加还是编辑)
dialog.value.open({}):拿到组件,调用open方法
const onAddChannel = () => {
dialog.value.open({})
}
const onEditChannel = (row) => {
dialog.value.open(row)
}