feat:移植工程

This commit is contained in:
2025-06-10 16:20:21 +08:00
commit 829df0b1d1
356 changed files with 43395 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
<template>
<div class="admin-module">
<div class="button-container">
<el-button type="primary" @click="addNewBlogClick">新增文章</el-button>
</div>
<el-table
:data="blogStore.blogList"
border stripe
:header-cell-style="tableHeaderStyle()"
:cell-style="tableCellStyle()"
:row-style="tableRowStyle()"
class="card-item">
<el-table-column type="index" align="center" width="50" />
<el-table-column prop="title" label="标题" align="center" min-width="10%"/>
<el-table-column prop="category" label="类别" align="center" min-width="10%" />
<el-table-column label="精品" align="center" min-width="10%">
<template #default="scope">
<el-tag type="success" v-if="scope.row.isGreat"></el-tag>
<el-tag type="danger" v-else></el-tag>
</template>
</el-table-column>
<el-table-column label="置顶" align="center" min-width="10%">
<template #default="scope">
<el-tag type="success" v-if="scope.row.topValue > 1"></el-tag>
<el-tag type="danger" v-else></el-tag>
</template>
</el-table-column>
<el-table-column prop="wordCount" label="字数" align="center" min-width="10%" />
<el-table-column prop="visitCount" label="阅读次数" align="center" min-width="10%" />
<el-table-column fixed="right" label="操作" align="center" width="200">
<template #default="scope">
<el-button type="primary" @click="editBlogClick(scope.row)">编辑</el-button>
<el-button type="danger" @click="deleteBlogClick(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="blogStore.adminPageInfo.currentPage"
:page-size="blogStore.adminPageInfo.pageSize"
:total="blogStore.adminPageInfo.totalSize"
layout="total, prev, pager, next"
@change="changePageClick"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue'
import { useBlogStore } from '@/store/blog.ts'
import { IBlog } from '@/types/blog.ts'
import { commonElMessageBox, tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
import { useRouter } from 'vue-router'
const router = useRouter()
const blogStore = useBlogStore()
onMounted(async () => {
await blogStore.queryAdminBlog()
})
/**
* 点击切换博客页数按钮
* @param page 页码
* @param pageSize 每页大小
*/
const changePageClick = async (page: number, pageSize: number) => {
blogStore.adminPageInfo.currentPage = page
blogStore.adminPageInfo.pageSize = pageSize
await blogStore.queryAdminBlog()
}
/**
* 点击新增博客按钮
*/
const addNewBlogClick = () => {
router.push({ name: 'AdminBlogDetailId', params: { id: 0 } })
}
/**
* 点击编辑博客按钮
* @param blog 博客
*/
const editBlogClick = async (blog: IBlog) => {
await router.push({ name: 'AdminBlogDetailId', params: { id: blog.id } })
}
/**
* 点击删除博客按钮
* @param blog 博客
*/
const deleteBlogClick = (blog: IBlog) => {
blogStore.blogApiType = 'DELETE'
commonElMessageBox('是否确认删除该博客?').then(() => {
blogStore.handleBlogApi(blog.id)
})
}
</script>

View File

@@ -0,0 +1,82 @@
<template>
<div class="admin-module">
<div class="button-container">
<el-button type="primary" @click="addNewCategoryClick">新增类别</el-button>
</div>
<el-table
:data="blogStore.blogCategoryList"
border stripe
:header-cell-style="tableHeaderStyle()"
:cell-style="tableCellStyle()"
:row-style="tableRowStyle()"
style="width: 50%"
class="card-item">
<el-table-column type="index" align="center" width="50" />
<el-table-column prop="name" label="名称" align="center" min-width="10%"/>
<el-table-column prop="count" label="数量" align="center" min-width="10%" />
<el-table-column fixed="right" label="操作" align="center" width="200">
<template #default="scope">
<el-button type="primary" @click="editCategoryClick(scope.row)">编辑</el-button>
<el-button type="danger" @click="deleteCategoryClick(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue'
import { useBlogStore } from '@/store/blog.ts'
import { IBlogCategory } from '@/types/blog.ts'
import { ElMessageBox } from 'element-plus'
import { tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
const blogStore = useBlogStore()
onMounted(async () => {
await blogStore.queryBlogCategory()
})
/**
* 点击新增类别按钮
*/
const addNewCategoryClick = () => {
ElMessageBox.prompt('请输入类别名称', '新增类别', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputPlaceholder: '请输入类别名称',
inputPattern: /^.{0,10}$/,
inputErrorMessage: '请输入10字以内的类别名称'
})
.then(({ value }) => {
console.log(value)
})
}
/**
* 点击编辑类别按钮
* @param category 类别
*/
const editCategoryClick = async (category: IBlogCategory) => {
ElMessageBox.prompt('请输入类别名称', '编辑类别', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputValue: category.name,
inputPlaceholder: '请输入类别名称',
inputPattern: /^.{0,10}$/,
inputErrorMessage: '请输入10字以内的类别名称'
})
.then(({ value }) => {
console.log(value)
})
}
/**
* 点击删除类别按钮
* @param category 类别
*/
const deleteCategoryClick = (category: IBlogCategory) => {
}
</script>

View File

@@ -0,0 +1,158 @@
<template>
<div class="admin-module admin-blog-detail">
<el-form ref="formRef" :model="blogStore.newBlog"
:inline="true" :rules="rules"
class="blog-form">
<el-form-item label="标题" name="title" prop="title">
<el-input v-model="blogStore.newBlog.title"
:maxlength="20" show-word-limit clearable
style="width: 300px"/>
</el-form-item>
<el-form-item label="分类" name="category" prop="category">
<el-select
v-model="blogStore.newBlog.category"
filterable allow-create placeholder="请选择分类" clearable
@focus="focusCategoryEvent" style="width: 150px">
<el-option
v-for="(value, index) in blogStore.blogCategoryList"
:key="index" :label="value.name" :value="value.name"/>
</el-select>
</el-form-item>
<el-form-item label="置顶值">
<el-input-number v-model="blogStore.newBlog.topValue"
:min="1" :max="10" style="width: 120px"/>
</el-form-item>
<el-form-item label="精品">
<el-switch v-model="blogStore.newBlog.isGreat"/>
</el-form-item>
</el-form>
<MdEditor v-model="blogStore.newBlog.content" @onUploadImg="onUploadImg"/>
<div class="button-container">
<el-button type="primary" @click="submitFormEvent">保存</el-button>
</div>
<div class="back-button">
<el-button type="primary" @click="backClick">返回</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { useBlogStore } from '@/store/blog.ts'
import { MdEditor } from 'md-editor-v3'
import 'md-editor-v3/lib/style.css'
import { ElLoading, ElMessage, FormInstance, FormRules } from 'element-plus'
import { blogRules } from '@/utils/element/elRules.ts'
import { useRouter, useRoute } from 'vue-router'
import { validateBlogImage } from '@/utils/blog/blogUtil'
import { doUploadFile } from 'vue3-common/utils/fileUtil'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
const rules = reactive<FormRules>(blogRules)
const formRef = ref<FormInstance>()
const route = useRoute()
const router = useRouter()
const blogStore = useBlogStore()
const focusCategoryEvent = async () => {
await blogStore.queryAdminBlogCategory()
}
onMounted(async () => {
await nextTick(() => {
formRef.value?.clearValidate()
})
const blogId = route.params.id as number
if (blogId > 0) {
blogStore.blogApiType = 'UPDATE'
await blogStore.queryAdminBlogById(route.params.id as number)
blogStore.newBlog = blogStore.currentBlog
} else {
blogStore.blogApiType = 'ADD'
blogStore.newBlog = blogStore.getEmptyBlog()
}
})
/**
* 校验博客
*/
const validateBlog = (): boolean => {
if (blogStore.newBlog.content === '') {
ElMessage.error('请输入博客内容')
return false
}
return true
}
/**
* 提交表单事件
*/
const submitFormEvent = () => {
// 表单校验
formRef.value?.validate().then(async () => {
if (validateBlog()) {
await blogStore.handleBlogApi()
await router.push('/admin/blog/article')
}
})
}
const onUploadImg = async (files: File[], callback) => {
const loading = ElLoading.service({
lock: true,
text: '正在上传图片 请等待',
background: 'rgba(0, 0, 0, 0.7)'
})
if (validateBlogImage(files)) {
const result = await doUploadFile(fileServiceUrl, files[0], blogStore.blogImagePath, false)
// 显示在markdown中
// 注意这里需要是数组的形式
const urlArray = []
urlArray.push({
url: `${serviceFileRootPath}${result.url.replace(/\\/g, '/')}`,
alt: '暂无图片',
title: files[0].name
})
callback(urlArray)
}
loading.close()
}
const backClick = () => {
router.push('/admin/blog/article')
}
</script>
<style lang="scss">
.admin-blog-detail {
position: relative;
@media (max-width: 1200px) {
.blog-form {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
.md-editor {
height: 80vh;
}
.back-button {
position: absolute;
top: 10px;
left: 10px;
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="admin-module">
<el-table
:data="blogInfo.blogVisitList"
border stripe
:header-cell-style="tableHeaderStyle()"
:cell-style="tableCellStyle()"
:row-style="tableRowStyle()"
style="width: 100%"
class="card-item">
<el-table-column type="index" align="center" width="50" />
<el-table-column prop="ip" label="IP" align="center" min-width="10%"/>
<el-table-column prop="os" label="平台" align="center" min-width="10%" />
<el-table-column prop="browser" label="浏览器" align="center" min-width="10%" />
<el-table-column prop="uri" label="路径" align="center" min-width="10%" />
<el-table-column prop="title" label="博客标题" align="center" min-width="10%" />
<el-table-column prop="visitTime" label="访问时间" align="center" min-width="10%" />
</el-table>
<el-pagination
v-model:current-page="blogInfo.pageInfo.currentPage"
:page-size="blogInfo.pageInfo.pageSize"
:total="blogInfo.pageInfo.totalSize"
layout="total, prev, pager, next"
@change="changePageClick"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive } from 'vue'
import { IBlogVisit } from '@/types/blog.ts'
import { queryBlogVisitApi } from '@/apis/blog.ts'
import { tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
const blogInfo = reactive({
blogVisitList: [] as IBlogVisit[],
pageInfo: {
currentPage: 1,
pageSize: 15,
totalSize: 1
}
})
onMounted(async () => {
await queryBlogVisit()
})
/**
* 切换页码
* @param page 当前页码
* @param pageSize 页码大小
*/
const changePageClick = async (page: number, pageSize: number) => {
blogInfo.pageInfo.currentPage = page
blogInfo.pageInfo.pageSize = pageSize
await queryBlogVisit()
}
/**
* 查看博客访问记录
*/
const queryBlogVisit = async () => {
const { currentPage, pageSize } = blogInfo.pageInfo
const result = await queryBlogVisitApi(currentPage, pageSize)
blogInfo.blogVisitList = result.records
blogInfo.pageInfo.totalSize = result.total
}
</script>