Vue3+TypeScript+element-plus的文章管理项目(配后端接口)-退出及文章管理页面

首先,需要在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)

最后,需要接口文档的也可以找我

相关推荐
程序员黑豆1 小时前
鸿蒙应用开发:Flex 组件从入门到实战
前端·华为·harmonyos
先吃饱再说1 小时前
React 组件设计:从 Props 传递到组件封装
前端·react.js
独立开阀者_FwtCoder1 小时前
最近做了一个健身小程序:智形健身助手,健身的佬们来提点意见
前端·javascript·github
舒灿1 小时前
喵ing Tab 1.0.0 正式发布
前端·ai编程
葡萄城技术团队1 小时前
当表格数据来自外部系统:SpreadJS 如何让异步公式一键刷新
前端
小徐_23331 小时前
Open Wot 1.0.5 发布:让 AI 接入 wot-ui,只需要两条命令
前端·uni-app·ai编程
Android研究员1 小时前
Android进阶之事件分发机制深度剖析
android·前端·面试
Csvn2 小时前
🛡️ 前端水印方案全解析——从「能被 F12 删掉」到「删不掉」的反破解实战
前端
学网安的肆伍2 小时前
【046-WEB攻防篇】注入工具&SQLMAP&Tamper编写&指纹修改&高权限操作&目录架构
前端·架构