Browse Source

add news

master
panda 8 months ago
parent
commit
472066e766
  1. 1
      src/api/admin/index.ts
  2. 203
      src/components/blogs/ceshi.vue
  3. 36
      src/main.ts
  4. 4
      src/views/admin/BlogManageView.vue
  5. 85
      src/views/blog/blogcontent/BlogDetailView.vue

1
src/api/admin/index.ts

@ -11,7 +11,6 @@ export interface blogInterface {
typename: string,
labelnames: string
}
export interface classticInterface {
key: string,
id?: number,

203
src/components/blogs/ceshi.vue

@ -1,30 +1,189 @@
<template>
<a-form-item name="select-multiple" label="Select[multiple]">
<a-select v-model:value="formState.labels" mode="multiple" placeholder="Please select favourite colors">
<a-select-option :value="type.id" v-for="type in typelist" >
{{ type.color }}
</a-select-option>
</a-select>
</a-form-item>
<div>
<div
v-for="anchor in titles"
:style="{ padding: `10px 0 10px ${anchor.indent}px` }"
@click="handleAnchorClick(anchor)"
:key="anchor.lineIndex"
>
<a style="cursor: pointer">{{ anchor.title }}</a>
</div>
<v-md-preview :text="text" ref="preview" />
</div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const formState = ref({
labels: [2, 3] // typelist id
const text = `
# heading 1
contentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontent
## heading 2
contentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontent
### heading 3
contentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontentcontentcontentcontent
contentcontentcontent
contentcontentcontent
contentcontentcontent
`;
const preview = ref<any>(null);
const titles = ref<{ title: string; lineIndex: string; indent: number }[]>([]);
onMounted(() => {
const anchors = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
const titlesArray = Array.from(anchors).filter((title) => !!title.innerText.trim());
if (!titlesArray.length) {
titles.value = [];
return;
}
const hTags = Array.from(new Set(titlesArray.map((title) => title.tagName))).sort();
titles.value = titlesArray.map((el) => ({
title: el.innerText,
lineIndex: el.getAttribute('data-v-md-line') || '', //
indent: 20 * (parseInt(el.tagName.substring(1)) - 1) //
}));
});
const typelist = ref([
{
id: 2,
color: 'red'
},
{
id: 3,
color: 'green'
function handleAnchorClick(anchor: { title: string; lineIndex: string }) {
if (preview.value) {
const heading = preview.value.$el.querySelector(`[data-v-md-line="${anchor.lineIndex}"]`);
if (heading) {
preview.value.scrollToTarget({
target: heading,
scrollContainer: window,
top: 60,
});
} else {
console.error(`Heading element not found for lineIndex: ${anchor.lineIndex}`);
}
} else {
console.error('Preview component not initialized.');
}
]);
}
</script>
<style></style>

36
src/main.ts

@ -6,11 +6,17 @@ import router from './router'
import Antd from 'ant-design-vue';
import Simplebar from 'simplebar-vue';
import 'simplebar-vue/dist/simplebar.min.css';
// 进阶
import VMdEditor from '@kangc/v-md-editor/lib/codemirror-editor';
import '@kangc/v-md-editor/lib/style/codemirror-editor.css';
import githubTheme from '@kangc/v-md-editor/lib/theme/github.js';
import '@kangc/v-md-editor/lib/theme/style/github.css';
// 预览
import VMdPreview from '@kangc/v-md-editor/lib/preview';
import '@kangc/v-md-editor/lib/style/preview.css';
// 表情插件
import createEmojiPlugin from '@kangc/v-md-editor/lib/plugins/emoji/index';
import '@kangc/v-md-editor/lib/plugins/emoji/emoji.css';
// highlightjs
import hljs from 'highlight.js';
@ -35,14 +41,42 @@ import 'codemirror/addon/scroll/simplescrollbars';
import 'codemirror/addon/scroll/simplescrollbars.css';
// style
import 'codemirror/lib/codemirror.css';
// 引入 todo-list 插件及其样式(任务列表 例:- [x] Task)
import createTodoListPlugin from '@kangc/v-md-editor/lib/plugins/todo-list/index';
import '@kangc/v-md-editor/lib/plugins/todo-list/todo-list.css';
// 代码复制
import createCopyCodePlugin from '@kangc/v-md-editor/lib/plugins/copy-code/index';
import '@kangc/v-md-editor/lib/plugins/copy-code/copy-code.css';
// 内容定位
import createAlignPlugin from '@kangc/v-md-editor/lib/plugins/align';
import createLineNumbertPlugin from '@kangc/v-md-editor/lib/plugins/line-number/index';
import createTipPlugin from '@kangc/v-md-editor/lib/plugins/tip/index';
import '@kangc/v-md-editor/lib/plugins/tip/tip.css';
VMdEditor.Codemirror = Codemirror;
VMdEditor.use(githubTheme, {
Hljs: hljs,
});
VMdPreview.use(githubTheme, {
Hljs: hljs,
});
VMdEditor.use(createTodoListPlugin());
VMdEditor.use(createEmojiPlugin());
VMdEditor.use(createAlignPlugin());
VMdEditor.use(createLineNumbertPlugin());
VMdEditor.use(createTipPlugin());
VMdPreview.use(createCopyCodePlugin());
VMdPreview.use(createEmojiPlugin());
VMdPreview.use(createAlignPlugin());
VMdPreview.use(createTodoListPlugin());
VMdPreview.use(createLineNumbertPlugin());
VMdPreview.use(createTipPlugin());
const app = createApp(App)
app.component('Simplebar', Simplebar);
app.use(VMdEditor);
app.use(VMdPreview);
app.use(createPinia())
app.use(router)
app.use(Antd)

4
src/views/admin/BlogManageView.vue

@ -36,7 +36,9 @@
<a-input v-model:value="formState.imglink" placeholder="请输入文图地址" />
</a-form-item>
</a-flex>
<v-md-editor v-model="formState.blogcontent" height="800px"></v-md-editor>
<v-md-editor v-model="formState.blogcontent" height="800px"
left-toolbar="undo redo clear | h bold italic strikethrough quote | ul ol table hr | link image code | save | tip | emoji"
></v-md-editor>
<a-form-item label="博客备注" name="descr" class="desrpad">
<a-textarea v-model:value="formState.descr" />
</a-form-item>

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

@ -1,12 +1,81 @@
<template>
<div>{{ route.params.id }}</div>
</template>
<div id="blogDetail">
<div v-for="anchor in titles" :style="{ padding: `10px 0 10px ${anchor.indent * 20}px` }"
@click="handleAnchorClick(anchor)" :key="anchor.lineIndex">
<a style="cursor: pointer">{{ anchor.title }}</a>
</div>
<v-md-preview v-if="text.blogcontent" :text="text.blogcontent" v-bind="previewProps" />
</div>
</template>
<script setup lang='ts'>
import { useRoute } from 'vue-router';
const route=useRoute()
</script>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { get } from '@/tools/request';
<style>
const route = useRoute();
const text = ref({
blogtitle: "",
blogcontent: ""
});
const titles = ref<{ title: string; lineIndex: string; indent: number }[]>([]);
const preview = ref<any>(null);
// Props for v-md-preview component
const previewProps = {
leftToolbar: "undo redo clear | h bold italic strikethrough quote | ul ol table hr | link image code | save | tip | emoji"
};
// Fetch blog content
const blogOneList = async () => {
try {
const response = await get(`/blogs/list/${route.params.id}`);
if (response.data.data) {
text.value.blogcontent = response.data.data.blogcontent;
}
} catch (error) {
console.error("Error fetching blog content:", error);
}
};
// Process headings after blog content is fetched
const processHeadings = () => {
const anchors = document.querySelectorAll('#blogDetail h1, #blogDetail h2, #blogDetail h3, #blogDetail h4, #blogDetail h5, #blogDetail h6');
const titlesArray = Array.from(anchors).filter((title) => !!title.innerText.trim());
if (!titlesArray.length) {
titles.value = [];
return;
}
const hTags = Array.from(new Set(titlesArray.map((title: any) => title.tagName))).sort();
titles.value = titlesArray.map((el: any) => ({
title: el.innerText,
lineIndex: el.getAttribute('data-v-md-line') || '',
indent: hTags.indexOf(el.tagName),
}));
};
// Handle anchor click
const handleAnchorClick = (anchor: { title: string; lineIndex: string }) => {
const heading = document.querySelector(`#blogDetail [data-v-md-line="${anchor.lineIndex}"]`);
if (heading) {
heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
};
// Lifecycle hook to fetch blog content and process headings
onMounted(async () => {
await blogOneList();
processHeadings();
});
</script>
<style scoped>
#blogDetail {
width: 45%;
}
</style>
</style>
Loading…
Cancel
Save