Commit f0392c1d by pangchong

feat: 富文本样式

parent 2fbd303c
...@@ -7,19 +7,20 @@ export {} ...@@ -7,19 +7,20 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
NAlert: typeof import('naive-ui')['NAlert']
NButton: typeof import('naive-ui')['NButton'] NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard'] NCard: typeof import('naive-ui')['NCard']
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable'] NDataTable: typeof import('naive-ui')['NDataTable']
NInput: typeof import('naive-ui')['NInput']
NLayout: typeof import('naive-ui')['NLayout'] NLayout: typeof import('naive-ui')['NLayout']
NLayoutContent: typeof import('naive-ui')['NLayoutContent'] NLayoutContent: typeof import('naive-ui')['NLayoutContent']
NLayoutFooter: typeof import('naive-ui')['NLayoutFooter'] NLayoutFooter: typeof import('naive-ui')['NLayoutFooter']
NLayoutHeader: typeof import('naive-ui')['NLayoutHeader'] NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NResult: typeof import('naive-ui')['NResult'] NResult: typeof import('naive-ui')['NResult']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin'] NSwitch: typeof import('naive-ui')['NSwitch']
NTable: (typeof import('naive-ui'))['NTable'] NTree: typeof import('naive-ui')['NTree']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
} }
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
"dependencies": { "dependencies": {
"@types/qs": "^6.9.15", "@types/qs": "^6.9.15",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"less": "^4.2.0", "less": "^4.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
......
<template> <template>
<n-config-provider class="w-full h-full" :theme="appStore.theme" :locale="zhCN" :date-locale="dateZhCN" :theme-overrides="themeOverrides"> <n-config-provider class="w-full h-full" :theme="appStore.theme" :locale="zhCN" :date-locale="dateZhCN" :theme-overrides="themeOverrides">
<router-view></router-view> <n-message-provider>
<router-view></router-view>
</n-message-provider>
</n-config-provider> </n-config-provider>
</template> </template>
......
...@@ -5,6 +5,7 @@ const routes: Array<RouteRecordRaw> = [ ...@@ -5,6 +5,7 @@ const routes: Array<RouteRecordRaw> = [
path: '/', path: '/',
component: () => import(/* webpackChunkName: "Layout" */ '@/views/layout/index.vue'), component: () => import(/* webpackChunkName: "Layout" */ '@/views/layout/index.vue'),
name: 'Layout', name: 'Layout',
redirect: '/editor',
children: [ children: [
{ {
path: '/404', path: '/404',
...@@ -19,10 +20,16 @@ const routes: Array<RouteRecordRaw> = [ ...@@ -19,10 +20,16 @@ const routes: Array<RouteRecordRaw> = [
}, },
{ {
path: '/theme', path: '/theme',
component: () => import(/* webpackChunkName: "Login" */ '@/views/example/theme.vue'), component: () => import(/* webpackChunkName: "Theme" */ '@/views/example/theme.vue'),
name: 'Theme', name: 'Theme',
meta: { title: '主题' }, meta: { title: '主题' },
children: [] children: []
},
{
path: '/editor',
name: 'Editor',
component: () => import(/* webpackChunkName: "Editor" */ '@/views/editor/index.vue'),
meta: { title: '在线编辑器' }
} }
] ]
}, },
......
...@@ -28,6 +28,10 @@ const useAppStore = defineStore('app', { ...@@ -28,6 +28,10 @@ const useAppStore = defineStore('app', {
const cssVarName = `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}` const cssVarName = `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`
document.body.style.setProperty(cssVarName, value as string) document.body.style.setProperty(cssVarName, value as string)
} }
document.body.style.setProperty('--w-e-textarea-bg-color', themeData.baseColor)
document.body.style.setProperty('--w-e-toolbar-bg-color', themeData.baseColor)
document.body.style.setProperty('--w-e-textarea-color', themeData.textColorBase)
document.body.style.setProperty('--w-e-toolbar-border-color', themeData.borderColor)
} }
}, },
persist: false persist: false
......
<template>
<div class="z-10 h-full flex flex-col">
<!-- 工具栏 -->
<Toolbar :editor="editorRef" :editorId="editorId" class="border-b border-solid border-borderColor" />
<!-- 编辑器 -->
<div class="p-[15px] flex flex-1 overflow-hidden">
<slot name="left"></slot>
<n-card class="flex-1" content-class="h-full !p-0">
<Editor
v-model="valueHtml"
:defaultConfig="editorConfig"
:editorId="editorId"
:style="editorStyle"
@on-change="handleChange"
@on-created="handleCreated"
/>
</n-card>
</div>
</div>
</template>
<script setup lang="ts">
import { IDomEditor, IEditorConfig } from '@wangeditor/editor'
// @ts-ignore
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { isNumber } from 'lodash'
type InsertFnType = (url: string, alt: string, href: string) => void
const ps = defineProps({
modelValue: {
type: String,
default: ''
},
editorId: {
type: String,
default: 'wangeEditor-1'
},
url: {
type: String,
default: ''
},
height: {
type: [String, Number],
default: '100%'
},
editorConfig: {
type: Object,
default: () => {
return undefined
}
},
readonly: {
type: Boolean,
default: false
}
})
const es = defineEmits(['change', 'created', 'update:modelValue'])
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef<IDomEditor>()
const valueHtml = ref('')
watch(
() => ps.modelValue,
(val: string) => {
if (val === unref(valueHtml)) return
valueHtml.value = val
},
{
immediate: true
}
)
// 监听
watch(
() => valueHtml.value,
(val: string) => {
es('update:modelValue', val)
}
)
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor
es('created', editor)
}
// 编辑器配置
const message = useMessage()
const editorConfig = computed((): IEditorConfig => {
return Object.assign(
{
placeholder: '请输入内容...',
readOnly: ps.readonly,
customAlert: (s: string, t: string) => {
switch (t) {
case 'success':
message.success(s)
break
case 'info':
message.info(s)
break
case 'warning':
message.warning(s)
break
case 'error':
message.error(s)
break
default:
message.info(s)
break
}
},
autoFocus: false,
scroll: true,
MENU_CONF: {
['uploadImage']: {
server: ps.url,
// 单个文件的最大体积限制,默认为 2M
maxFileSize: 5 * 1024 * 1024,
// 最多可上传几个文件,默认为 100
maxNumberOfFiles: 10,
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
allowedFileTypes: ['image/*'],
// 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
meta: { updateSupport: 0 },
// 将 meta 拼接到 url 参数中,默认 false
metaWithUrl: true,
// 自定义增加 http header
headers: {
Accept: '*',
Authorization: `Bearer`
},
// 跨域是否传递 cookie ,默认为 false
withCredentials: true,
// 超时时间,默认为 10 秒
timeout: 5 * 1000, // 5 秒
// form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
fieldName: 'file',
// 上传之前触发
onBeforeUpload(file: File) {
console.log(file)
return file
},
// 上传进度的回调函数
onProgress(progress: number) {
// progress 是 0-100 的数字
console.log('progress', progress)
},
onSuccess(file: File, res: any) {
console.log('onSuccess', file, res)
},
onFailed(file: File, res: any) {
alert(res.message)
console.log('onFailed', file, res)
},
onError(file: File, err: any, res: any) {
alert(err.message)
console.error('onError', file, err, res)
},
// 自定义插入图片
customInsert(res: any, insertFn: InsertFnType) {
insertFn(res.data, 'image', res.data)
}
}
},
uploadImgShowBase64: true
},
ps.editorConfig || {}
)
})
const editorStyle = computed(() => {
return {
height: isNumber(ps.height) ? `${ps.height}px` : ps.height
}
})
// 回调函数
const handleChange = (editor: IDomEditor) => {
es('change', editor)
}
// 组件销毁时,及时销毁编辑器
onBeforeUnmount(() => {
const editor = unref(editorRef.value)
// 销毁,并移除 editor
editor?.destroy()
})
defineExpose({
editor: editorRef
})
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>
<template>
<div class="flex flex-col h-full">
<div class="p-[10px]">
<n-input v-model:value="searchKey" placeholder="搜索" />
</div>
<div class="flex-auto overflow-auto">
<n-tree :pattern="searchKey" :data="treeData" block-line />
</div>
</div>
</template>
<script setup lang="ts">
import { searchKey, treeData } from '../constants'
</script>
import type { TreeOption } from 'naive-ui'
// 菜单相关
export const searchKey = ref('')
export const treeData: TreeOption[] = [
{
label: '0',
key: '0',
children: [
{
label: '0-0',
key: '0-0',
children: [
{ label: '0-0-0', key: '0-0-0' },
{ label: '0-0-1', key: '0-0-1' }
]
},
{
label: '0-1',
key: '0-1',
children: [
{ label: '0-1-0', key: '0-1-0' },
{ label: '0-1-1', key: '0-1-1' }
]
}
]
},
{
label: '1',
key: '1',
children: [
{
label: '1-0',
key: '1-0',
children: [
{ label: '1-0-0', key: '1-0-0' },
{ label: '1-0-1', key: '1-0-1' }
]
},
{
label: '1-1',
key: '1-1',
children: [
{ label: '1-1-0', key: '1-1-0' },
{ label: '1-1-1', key: '1-1-1' }
]
}
]
}
]
//编辑器相关
export const editorRef = ref()
export const formData = reactive({
html: ''
})
import { IDomEditor } from '@wangeditor/editor'
export const handleEditor = (editor: IDomEditor) => {
console.log(editor)
// const headers = editor.getElemsByTypePrefix('header')
// const menu = headers.map((header: any) => {
// const title = SlateNode.string(header)
// return {
// title,
// key: header.id,
// type: header.type
// }
// })
// treeData.value = buildTree(menu)
}
<template>
<ContentEditor ref="editorRef" v-model="formData.html" @change="handleEditor" @created="handleEditor">
<template #left>
<n-card class="h-full min-w-[300px] max-w-[300px] mr-[15px]" content-class="bg-baseColor !p-0 overflow-hidden">
<ContentTree></ContentTree>
</n-card>
</template>
</ContentEditor>
</template>
<script setup lang="ts">
import ContentEditor from './components/ContentEditor.vue'
import ContentTree from './components/ContentTree.vue'
import { editorRef, formData } from './constants'
import { handleEditor } from './functions'
</script>
<style lang="less" scoped></style>
<template> <template>
<n-layout class="h-full overflow-hidden"> <n-layout class="h-full overflow-hidden">
<n-layout-header></n-layout-header> <n-layout-header></n-layout-header>
<n-layout-content content-class="flex flex-col flex-auto overflow-x-auto" class="h-full flex flex-col"> <n-layout-content content-class="flex flex-col flex-auto overflow-x-auto bg-codeColor" class="h-full flex flex-col">
<router-view></router-view> <router-view></router-view>
</n-layout-content> </n-layout-content>
<n-layout-footer></n-layout-footer> <n-layout-footer></n-layout-footer>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment