首先,需要在pinia中封装退出的函数
TypeScript
async deleteAction() {
this.token = ''
this.userInfo = {}
removeToken()
router.push(`/login?redirectUrl=${router.currentRoute.value.path}`)
},
html
<div class="logout">
<el-popconfirm
title="登出确认"
confirm-button-text="确定"
cancel-button-text="取消"
hide-after="0"
@confirm="onLogOut"
>
<template #reference>
<el-icon :size="20" color="#999">
<SwitchButton />
</el-icon>
</template>
</el-popconfirm>
</div>
function onLogOut() {
userStore.deleteAction()
}
随后实现登出逻辑,删除本地和pinia中的token信息。
处理token过期
TypeScript
const RESPONSE_CODE = {
success: 10000,
unauthorized: 401,
}
// 添加响应拦截器
instance.interceptors.response.use(
function (response) {
// 2xx 范围内的状态码都会触发该函数。
const { data: resp } = response
if (resp.code === RESPONSE_CODE.success) {
return resp.data
}
return Promise.reject(new Error(resp.message))
},
function (error) {
const userStore = useUserStore()
// 超出 2xx 范围的状态码都会触发该函数。
const {
status,
data: { message },
} = error.response
if (status === RESPONSE_CODE.unauthorized) {
ElMessage.error(message)
userStore.deleteAction()
} else {
return Promise.reject(error)
}
},
)
文章管理模块
html
<template>
<div class="article-box">
<el-breadcrumb :separator-icon="ArrowRight">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>文章管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card>
<template #header>
<div class="card-header">
<span>共 {{ total }} 条记录</span>
<el-button round type="primary" @click="onOpen(ARTICLE_OPS_TYPE.add, '')">
<el-icon><Plus /></el-icon>
<span>新增文章</span>
</el-button>
</div>
</template>
<el-table :data="articleList" border striped>
<el-table-column prop="stem" label="标题" width="300" />
<el-table-column prop="creator" label="作者" width="180" />>
<el-table-column prop="category" label="类别" wideh="140" />
<el-table-column prop="likeCount" label="点赞" width="140" sortable />
<el-table-column prop="views" label="浏览量" width="140" sortable />
<el-table-column prop="createdAt" label="创建时间" width="240" />
<el-table-column label="操作">
<template #default="{ row }">
<el-tooltip effect="dark" content="预览" placement="bottom">
<el-icon size="18" @click="onOpen(ARTICLE_OPS_TYPE.preview, row.id)"
><View
/></el-icon>
</el-tooltip>
<el-tooltip effect="dark" content="编辑" placement="bottom">
<el-icon size="18" @click="onOpen(ARTICLE_OPS_TYPE.edit, row.id)"><Edit /></el-icon>
</el-tooltip>
<el-tooltip effect="dark" content="删除" placement="bottom">
<el-icon size="18" @click="onDel(row.id)"><Delete /></el-icon>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-pagination
:current-page="query.current"
@current-change="onCurrentChange"
background
layout="prev, pager, next"
:total="total"
/>
</template>
</el-card>
<el-drawer v-model="drawer" size="45%" :title="activeTitle" direction="rtl">
<ArticleOps
:opsType="currentOpsType"
:opsId="articleId"
:key="articleId"
@finish="onFinish"
@delete="onDelete"
/>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { ArrowRight, Delete, Edit, Plus } from '@element-plus/icons-vue'
import { getArticleApi, deleteArticleApi } from '@/api/article'
import { type Query } from '@/types/allType'
import { reactive } from 'vue'
import { ref, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import ArticleOps from './components/ArticleOps.vue'
import { ARTICLE_OPS_TYPE } from '@/constant/article.ts'
const drawer = ref(false)
const TITLE_MAP = {
add: '新增',
preview: '预览',
edit: '编辑',
}
function onClose() {
drawer.value = false
}
function onDelete() {
onClose()
}
function onFinish() {
onClose()
articleApi(query)
}
const articleId = ref('')
const activeTitle = computed(() => {
return TITLE_MAP[currentOpsType.value] + '文章'
})
const currentOpsType = ref()
function onOpen(opsType: string, id: string) {
currentOpsType.value = opsType
drawer.value = true
articleId.value = id
}
async function onDel(id: number) {
try {
await ElMessageBox.confirm('确认删除吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
await deleteArticleApi(id)
ElMessage.success('删除成功')
await articleApi(query)
} catch (e) {}
}
function onCurrentChange(newvalue: any) {
query.current = newvalue
articleApi(query)
}
const query = reactive({
current: 1,
pageSize: 10,
} as Query)
const articleList = ref([])
const total = ref()
articleApi(query)
async function articleApi(query: Query) {
const api = (await getArticleApi(query)) as any
articleList.value = api.rows
total.value = api.total
}
</script>
<style scoped lang="scss">
.article-box {
.el-card {
margin-top: 25px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
}
.el-pagination {
display: flex;
justify-content: center;
}
:deep(.el-card__footer) {
border-top: none;
margin-top: -15px;
}
.el-table {
.el-icon:nth-child(2) {
margin: 0 15px;
}
}
}
}
</style>
html
<script setup lang="ts">
import { ARTICLE_OPS_TYPE } from '@/constant/article.ts'
import { getArticleDetail } from '@/api/article'
import { reactive } from 'vue'
import { postArticleApi, putArticleApi } from '@/api/article'
import { ElMessage } from 'element-plus'
const emit = defineEmits(['finish', 'delete'])
const props = defineProps({
opsType: {
type: String,
default: '',
},
opsId: {
type: String,
default: '',
},
})
const articleForm = reactive({
id: '',
stem: '',
content: '',
})
getDetail()
async function getDetail() {
if (props.opsId) {
const req = (await getArticleDetail(props.opsId)) as any
articleForm.id = req.id
articleForm.stem = req.stem
articleForm.content = req.content
} else {
return
}
}
function deleteSubmit() {
emit('delete')
}
async function submit() {
if (props.opsId) {
await putArticleApi(articleForm)
} else {
await postArticleApi(articleForm)
articleForm.stem = ''
articleForm.content = ''
}
ElMessage.success(props.opsId ? '编辑成功' : '新增成功')
emit('finish')
}
</script>
<template>
<div>
<el-form lable-width="55px" v-if="props.opsType !== ARTICLE_OPS_TYPE.preview">
<el-form-item label="标题">
<el-input placeholder="请输入文章标题" v-model="articleForm.stem" />
</el-form-item>
<el-form-item label="内容">
<QuillEditor
theme="snow"
toolbar="full"
placeholder="请输入文章内容"
v-model:content="articleForm.content"
contentType="html"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">确认</el-button>
<el-button @click="deleteSubmit">取消</el-button>
</el-form-item>
</el-form>
<!-- 预览 -->
<div class="preview" v-else>
<h4>{{ articleForm.stem }}</h4>
<div v-html="articleForm.content"></div>
</div>
</div>
</template>
<style scoped lang="scss">
.el-form {
.el-input {
height: 36px;
}
:deep(.ql-container) {
width: 100%;
min-height: 200px;
}
.el-form-item:last-child {
margin-top: 100px;
}
}
</style>
同时在api中定义
TypeScript
function getArticleDetail(id: string) {
return instance({
method: 'get',
url: `/article/${id}`,
})
}
function postArticleApi(articleForm: articleForm) {
return instance({
method: 'post',
url: '/article',
data: articleForm,
})
}
function putArticleApi(articleForm: articleForm) {
return instance({
method: 'put',
url: `/article/${articleForm.id}`,
data: articleForm,
})
这里使用到了富文本需要安装,并注册
TypeScript
npm install @vueup/vue-quill@latest
TypeScript
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
app.component('QuillEditor', QuillEditor)
最后,需要接口文档的也可以找我