Browse Source

add new

master
panda 9 months ago
parent
commit
2770861871
  1. 4
      src/components/admin/MainWrapper.vue
  2. 98
      src/components/blogs/HomePage.vue
  3. 68
      src/components/blogs/ceshi.vue
  4. 5
      src/router/admin.ts
  5. 4
      src/router/blog.ts
  6. 48
      src/stores/blog.ts
  7. 1
      src/stores/counter.ts
  8. 138
      src/views/admin/blogmange/BlogFormView.vue
  9. 188
      src/views/admin/blogmange/BlogManageView.vue
  10. 11
      src/views/blog/blogcontent/BlogDetailView.vue
  11. 39
      src/views/blog/blogcontent/BlogListView.vue

4
src/components/admin/MainWrapper.vue

@ -7,7 +7,7 @@
<span v-if="!collapsed">{{ state.name }}</span>
</a-flex>
<a-menu v-model:openKeys="state.openKeys" v-model:selectedKeys="state.selectedKeys" mode="inline" theme="light"
:inline-collapsed="state.collapsed" :items="items" style="border-inline-end: none;">
:inline-collapsed="state.collapsed" :items="items" style="border-inline-end: none;" @click="menuItemclick">
</a-menu>
</div>
<div class="contentArea">
@ -59,7 +59,7 @@ const items = reactive([
{
key: '1',
icon: () => h(DashboardOutlined),
label: h('a',{href:"/admin/dashboard"},'仪表盘'),
label: "仪表盘",
title: '仪表盘',
url: '/admin/dashboard'
},

98
src/components/blogs/HomePage.vue

@ -53,7 +53,8 @@
</a-card>
<a-card hoverable title="常用链接">
<div class="button-group">
<a-button type="dashed" v-for="linkbutton in linkbuttons" :key="linkbutton.id">{{
<a-button type="dashed" v-for="linkbutton in linkbuttons" :key="linkbutton.id"
@click="comLinkClick(linkbutton.linkurl)">{{
linkbutton.linktext
}}</a-button>
</div>
@ -116,7 +117,10 @@ import 'APlayer/dist/APlayer.min.css';
import APlayer from 'APlayer';
import { createEcharts } from "@/hooks/intex"
import { useRouter, useRoute } from 'vue-router';
import iconComponents from "@/assets/index"
import iconComponents from "@/assets/index";
import { get } from "@/tools/request";
import { number } from 'echarts';
const router = useRouter()
const route = useRoute()
@ -252,24 +256,29 @@ const downScroll = () => {
//
const activeKey = ref(['']);
const customStyle = 'background: #F5F5F5;border-radius: 4px;margin-bottom: 12px;border: 0;overflow: hidden';
const panels = reactive([
{
id: "1",
header: "内容1",
text: "内容1"
},
{
id: "2",
header: "内容2",
text: "内容2"
},
{
id: "3",
header: "内容3",
text: "内容3"
interface panelList {
id: number,
header: string,
text: string
}
const panels = ref<panelList[]>([])
const panelData = async () => {
try {
const response = await get("/classtic/list");
if (response) {
panels.value = response.data.data.map((item: any) => ({
id: item.id,
header: item.header,
text: item.TEXT,
}));
} else {
console.error("Response data structure is not as expected:");
}
]
)
} catch (error) {
console.error("Failed to fetch data", error);
}
}
// console.log(panels)
//
const buttons = ref([
{ icon: iconComponents.CravatarLined, url: 'https://cravatar.cn/' },
@ -282,25 +291,36 @@ const handleClick = (url: string) => {
window.open(url)
}
//
const linkbuttons = reactive([
{
id: "1",
linktext: "测试"
},
{
id: "2",
linktext: "开发"
},
{
id: "3",
linktext: "运营"
},
{
id: "4",
linktext: "实施"
interface comlinkList {
id: number,
linktext: string,
linkurl: string
}
const linkbuttons = ref<comlinkList[]>([])
const comLinkData = async () => {
try {
const response = await get("/comlink/list");
if (response) {
linkbuttons.value = response.data.data.map((items: any) => ({
id: items.id,
linktext: items.linktext,
linkurl: items.linkurl
}))
} else {
console.log("response data is not exist")
}
} catch (error) {
console.error("Failed to fetch data", error);
}
])
}
const comLinkClick = (url: string) => {
if (url.startsWith('http://') || url.startsWith('https://')) {
window.open (url,"_blank"); // 使 window.location.href
} else {
router.push(url); // 使 Vue Router push
}
}
/**
* 右侧栏
*/
@ -424,6 +444,9 @@ const random = ref();
onMounted(() => {
scrollbar.value = document.querySelector('.simplebar-content-wrapper');
panelData()
comLinkData()
nextTick(() => {
const authorElement = document.querySelector('.author');
if (authorElement) {
@ -485,7 +508,8 @@ onMounted(() => {
}
});
createEcharts(heat, heatMapData);
scrollbar.value = document.querySelector('.simplebar-content-wrapper');
})
watch(
() => route.name,

68
src/components/blogs/ceshi.vue

@ -1,65 +1,11 @@
<template>
<a-menu v-model:selectedKeys="current" mode="horizontal" :items="items" />
<div>{{ }}</div>
</template>
<script lang="ts" setup>
import { h, ref } from 'vue';
import { MailOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons-vue';
import { MenuProps } from 'ant-design-vue';
const current = ref<string[]>(['app']);
const items = ref<MenuProps['items']>([
{
key: 'mail',
icon: () => h(MailOutlined),
label: 'Navigation One',
title: 'Navigation One',
},
{
key: 'app',
icon: () => h(AppstoreOutlined),
label: 'Navigation Two',
title: 'Navigation Two',
},
{
key: 'sub1',
icon: () => h(SettingOutlined),
label: 'Navigation Three - Submenu',
title: 'Navigation Three - Submenu',
children: [
{
type: 'group',
label: 'Item 1',
children: [
{
label: 'Option 1',
key: 'setting:1',
},
{
label: 'Option 2',
key: 'setting:2',
},
],
},
{
type: 'group',
label: 'Item 2',
children: [
{
label: 'Option 3',
key: 'setting:3',
},
{
label: 'Option 4',
key: 'setting:4',
},
],
},
],
},
{
key: 'alipay',
label: h('a', { href: 'https://antdv.com', target: '_blank' }, 'Navigation Four - Link'),
title: 'Navigation Four - Link',
},
]);
<script setup lang='ts'>
</script>
<style>
</style>

5
src/router/admin.ts

@ -10,6 +10,11 @@ const adminRoute:Array<RouteRecordRaw>=[
path:"dashboard",
name:"dashboard",
component:()=>import("@/views/admin/DashBoardView.vue")
},
{
path:"blogmanage",
name:"blogmanage",
component:()=>import("@/views/admin/blogmange/BlogManageView.vue")
}
]

4
src/router/blog.ts

@ -15,7 +15,7 @@ const blogRoute:Array<RouteRecordRaw>=[
{
path:"/blog",
name:"blog",
component:()=>import("@/views/blog/BlogContentView.vue")
component:()=>import("@/views/blog/blogcontent/BlogListView.vue")
},
{
path:"/diary",
@ -47,7 +47,7 @@ const blogRoute:Array<RouteRecordRaw>=[
{
path:"/wechatcode",
name:"wechatcode",
component:()=>import("@/components/blogs/WechatCode.vue"),
component:()=>import("@/components/blogs/WeChatCode.vue"),
},
]
export default blogRoute

48
src/stores/blog.ts

@ -0,0 +1,48 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { get } from "@/tools/request"
import dayjs from 'dayjs';
export const blogContentStore = defineStore("blog", () => {
interface blogInterface {
key:string,
id: number,
blogtitle: string,
create_at: Date,
readnum: number,
readminite: number,
wordcount: number,
img: string,
blogcontent: string,
typename: string,
labelname: string
}
const bloglist = ref<blogInterface[]>([])
async function blogList() {
try {
const response = await get("/blogs/list");
if (response) {
bloglist.value = response.data.data.map((items: any,index:any) => ({
key: (index + 1).toString(),
id: items.id,
blogtitle: items.blogtitle,
create_at: dayjs(items.create_at).format('YYYY-MM-DD HH:mm:ss'),
update_at: dayjs(items.update_at).format('YYYY-MM-DD HH:mm:ss'),
readnum: items.readnum,
readminite: items.readminite,
wordcount: items.wordcount,
img: items.img,
blogcontent: items.blogcontent,
typename: items.typename,
labelname: items.labelname
}))
} else {
console.log("bloglist is not exits")
}
} catch (error) {
console.log("bloglist is error")
}
}
return { blogList, bloglist }
})

1
src/stores/counter.ts

@ -10,3 +10,4 @@ export const useCounterStore = defineStore('counter', () => {
return { count, doubleCount, increment }
})

138
src/views/admin/blogmange/BlogFormView.vue

@ -0,0 +1,138 @@
<template>
<div id="content">
<a-form ref="formRef" :model="formState" :rules="rules">
<a-flex gap="large" justify="space-between">
<a-form-item ref="blogtitle" label="博客标题" name="blogtitle">
<a-input v-model:value="formState.blogtitle" placeholder="请输入博客标题" class="items" />
</a-form-item>
<a-form-item label="博客类型" name="blogtype">
<a-select v-model:value="formState.typeid" placeholder="请选择博客类型" class="items">
<a-select-option v-for="typeItem in typedata" :key="typeItem.key" :value="typeItem.id">{{
typeItem.typename }}</a-select-option>
</a-select>
</a-form-item>
</a-flex>
<v-md-editor v-model="content" height="900px"></v-md-editor>
<a-form-item label="博客备注" name="descr" class="desrpad">
<a-textarea v-model:value="formState.descr" />
</a-form-item>
<a-form-item :wrapper-col="{ span: 2, offset: 22 }">
<a-button @click="resetForm">取消</a-button>
<a-button style="margin-left: 24px" type="primary" @click="onSubmit">提交</a-button>
</a-form-item>
<div>
<div class="blog-content" v-html="formState.blogcontent"></div>
<!-- 其他表单元素 -->
</div>
</a-form>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from 'vue';
import type { UnwrapRef } from 'vue';
import type { Rule } from 'ant-design-vue/es/form';
import { post, get, put } from '@/tools/request';
import { useRouter, useRoute } from 'vue-router'
import type { TypeStateData } from '@/api/types'
const router = useRouter()
const route = useRoute()
interface FormState {
id?: number;
blogtitle: string;
typeid: number;
blogcontent: string;
descr: string;
}
const formRef = ref();
const formState: UnwrapRef<FormState> = reactive({
blogtitle: '',
typeid: 1,
blogcontent: "",
descr: '',
});
const rules: Record<string, Rule[]> = {
blogtitle: [
{ required: true, message: '请输入博客标题', trigger: 'change' },
{ min: 4, max: 20, message: '博客标题允许4-20个字符', trigger: 'blur' },
],
descr: [
{ required: false },
{ max: 255, message: '博客备注不得超过255个字符', trigger: 'change' },
],
};
const content = ref();
const typedata = ref<TypeStateData[]>([])
onMounted(async () => {
get("/types/list").then(response => {
typedata.value = response.data.data.map((items: any) => ({
...items
}))
})
const { id } = route.params
if (id) {
get(`/blogs/list/search/${id}`).then(response => {
const blog = response.data.data;
//
formState.id = blog.id;
formState.blogtitle = blog.blogtitle;
formState.typeid = blog.typeid;
formState.blogcontent = blog.blogcontent;
formState.descr = blog.descr;
// Vditor
console.log(blog.id)
if (blog.id) {
content.value=blog.blogcontent;
}
})
}
});
const onSubmit = () => {
formRef.value
.validate()
.then(async () => {
const formdata = {
blogtitle: formState.blogtitle,
typeid: formState.typeid,
blogcontent: content.value,
descr: formState.descr
}
if (formState.id) {
await put(`/blogs/update/${formState.id}`, formdata);
} else {
await post('/blogs/add', formdata);
}
})
router.push("/admin/blogmanage")
.catch((error: any) => {
console.log('error', error);
});
};
const resetForm = () => {
formRef.value.resetFields();
};
</script>
<style scoped>
.editor-container {
width: 100%;
}
.vditor {
width: 100%;
border: 1px solid #ccc;
}
.items {
width: calc(75vw/2);
}
#content {
padding: 24px 24px 0 24px;
}
.desrpad {
padding: 24px 0 0 0;
}
</style>

188
src/views/admin/blogmange/BlogManageView.vue

@ -0,0 +1,188 @@
<template>
<div class="content">
<div class="search">
<a-space>
<a-input v-model:value="searchList.blogtitle" placeholder="博客标题" />
<a-input v-model:value="searchList.typename" placeholder="分类名称" />
<a-range-picker :locale="locale" show-time @change="onChange" />
</a-space>
<a-space style="margin-left: 16px;">
<a-button @click="blogSearch">查询</a-button>
<a-button type="primary" ghost @click="blogAdd">新增</a-button>
</a-space>
</div>
<div class="table">
<a-table bordered :data-source="blogContent.bloglist" :columns="columns">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'operation'">
<a-space>
<div>
<a-button size="small" danger @click="showModal(record.key, record.id)">删除</a-button>
<a-modal v-model:open="open" title="提示" @ok="ackDelete(record)" ok-text="确认" cancel-text="取消" @cancel="unDelete">
<p>确认删除吗</p>
</a-modal>
</div>
<a-button size="small" type="primary" ghost @click="ackUpdate(record)">编辑</a-button>
<a-button size="small" type="primary" ghost @click="ackWatch(record)">预览</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
</div>
</template>
<script setup lang='ts'>
import { ref, reactive, onMounted } from 'vue';
import {blogContentStore} from "@/stores/blog"
import 'dayjs/locale/zh-cn';
import locale from 'ant-design-vue/es/date-picker/locale/zh_CN';
import dayjs from 'dayjs';
import { useRouter } from "vue-router"
import { get, remove } from '@/tools/request';
import { message } from 'ant-design-vue';
dayjs.locale('zh-cn');
const blogContent=blogContentStore()
const router = useRouter()
const searchList = reactive<{ blogtitle: string, typename: string, start_date: string, end_date: string }>({
blogtitle: '',
typename: '',
start_date: '',
end_date: ''
})
//
const onChange = (date: string, dateString: string[]) => {
if (date && dateString.length === 2) {
searchList.start_date = (dateString[0]);
searchList.end_date = (dateString[1]);
} else {
searchList.start_date = '';
searchList.end_date = '';
}
console.log(dateString[0])
};
const open = ref<boolean>(false);
const toDelete = reactive<{ key: string, id: number }>({
key: '',
id: 0
});
const showModal = (key: string, id: number) => {
open.value = true;
// key id ref 便使
toDelete.key = key;
toDelete.id = id;
};
//
const blogAdd = function () {
router.push('/admin/blogmanage/add')
}
//
onMounted(async () => {
// get("/blogs/list").then(response => {
// dataSource.value = (response as any).data.data.map((items: any, index: any) => ({
// key: (index + 1).toString(),
// id: items.id,
// blogtitle: items.blogtitle,
// typename: items.typename,
// blogcontent: items.blogcontent,
// create_at: dayjs(items.create_at).format('YYYY-MM-DD HH:mm:ss'), //
// update_at: dayjs(items.update_at).format('YYYY-MM-DD HH:mm:ss'), //
// descr: items.descr
// }))
// })
blogContent.blogList()
});
//
const blogSearch = () => {
get("/blogs/list/search", {
blogtitle: searchList.blogtitle,
typename: searchList.typename,
start_date: searchList.start_date,
end_date: searchList.end_date,
})
.then(response => {
dataSource.value = response.data.data.map((items: any, index: any) => ({
key: (index + 1).toString(),
blogtitle: items.blogtitle,
typename: items.typename,
blogcontent: items.blogcontent,
create_at: dayjs(items.create_at).format('YYYY-MM-DD HH:mm:ss'),
update_at: dayjs(items.update_at).format('YYYY-MM-DD HH:mm:ss'),
descr: items.descr
}));
})
.catch(error => {
console.error('There was an error seraching the blog!', error);
});
};
//
const ackDelete = async (record:{key:string,id:number}) => {
remove(`/blogs/delete/${toDelete.id}`).then((response) => {
if (response.status === 200) {
dataSource.value = dataSource.value.filter(item => item.key !== record.key);
open.value = false;
}
message.info("删除成功")
}).catch(error => {
console.error('There was an error deleting the blog!', error);
})
};
//
const ackUpdate =async (record: { key: string, id: number }) => {
router.push(`/admin/blogmanage/update/${record.id}`);
};
const ackWatch=async (record: { key: string, id: number }) => {
router.push(`/admin/blogmanage/watch`);
}
const unDelete=async ()=>{
message.warn("取消删除")
}
const columns = [
{
title: '序号',
dataIndex: 'key',
width: '5%',
},
{
title: '博客标题',
dataIndex: 'blogtitle',
width: '20%',
},
{
title: '博客分类',
dataIndex: 'typename',
},
{
title: '博客标签',
dataIndex: 'labelname',
},
{
title: '发布时间',
dataIndex: 'create_at',
},
{
title: '修改时间',
dataIndex: 'update_at',
},
{
title: '操作',
dataIndex: 'operation',
},
];
</script>
<style scoped>
.content {
padding: 24px 24px 0 24px;
}
.search {
margin: 0 0 24px 0;
}
</style>

11
src/views/blog/blogcontent/BlogDetailView.vue

@ -0,0 +1,11 @@
<template>
</template>
<script setup lang='ts'>
</script>
<style>
</style>

39
src/views/blog/BlogContentView.vue → src/views/blog/blogcontent/BlogListView.vue

@ -1,42 +1,42 @@
<template>
<div class="main">
<div class="mainContent" v-for="article in 5">
<a-badge-ribbon text="Hippies" color="black">
<div class="mainContent" v-for="article in blogContent.bloglist">
<a-badge-ribbon :text=article.labelid color="black">
<a-card hoverable>
<h2>操作系统的安装</h2>
<h2>{{article.blogtitle}}</h2>
<div class="tag-group">
<a-tag color="#E6E6FA">
<template #icon>
<RiLiLined />
</template>
{{ mainStatistic.mainDate }}
{{ article.create_at }}
</a-tag>
<a-tag color="#6495ED">
<template #icon>
<YanJingLined />
</template>
{{ mainStatistic.mainWatchCount }}
{{ article.readminite }}
</a-tag>
<a-tag color="#B0C4DE">
<template #icon>
<XieZiLined />
</template>
字数{{ mainStatistic.mainWordCount }}
字数{{ article.wordcount }}
</a-tag>
<a-tag color="#20B2AA">
<template #icon>
<YueDuLined />
</template>
阅读时长{{ mainStatistic.mainReadCount }}
阅读时长{{ article.readminite }}
</a-tag>
</div>
<div class="blog-content">
<div>
<a-image :preview="false" :width="200"
src="https://cdn.naccl.top/blog/blogHosting/2024/02/B01/f56c5bbe-469c-4eb7-a994-9281d6eed689.png" />
:src=article.img />
</div>
<div class="text-container">
这是内容
{{article.blogcontent}}
</div>
</div>
<div class="read-button">
@ -49,14 +49,12 @@
</template>
<script setup lang='ts'>
import { reactive } from 'vue';
const mainStatistic = reactive<{ mainDate: string, mainWatchCount: string, mainWordCount: string, mainReadCount: string }>({
mainDate: "2024-06-11",
mainWatchCount: "9999",
mainWordCount: "9999",
mainReadCount: "9999"
})
import { onMounted } from 'vue';
import { blogContentStore } from "@/stores/blog"
const blogContent = blogContentStore();
onMounted(() => {
blogContent.blogList();
});
</script>
<style scoped>
@ -87,6 +85,13 @@ const mainStatistic = reactive<{ mainDate: string, mainWatchCount: string, mainW
display: flex;
margin: 48px;
}
.main .blog-content>:first-child{
padding: 4px;
border: 2px solid #ccc;
display: inline-block;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.main .blog-content>:nth-child(2) {
overflow: hidden;
Loading…
Cancel
Save