提交 7db0c5c4 编写于 作者: V vben

fix: add an example of markdown embedded in the form #138

上级 10cd4fcd
......@@ -4,6 +4,7 @@
- 新增 `v-ripple`水波纹指令
- 新增左侧菜单混合模式
- 新增 markdown 嵌入表单内示例
### 🐛 Bug Fixes
......
......@@ -4,6 +4,7 @@
:wrapClass="`scrollbar__wrap`"
:viewClass="`scrollbar__view`"
class="scroll-container"
v-bind="$attrs"
>
<slot />
</Scrollbar>
......
......@@ -2,14 +2,25 @@
<div class="markdown" ref="wrapRef" />
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, unref, onUnmounted, nextTick, watchEffect } from 'vue';
import {
defineComponent,
ref,
onMounted,
unref,
onUnmounted,
nextTick,
// watch,
computed,
} from 'vue';
import Vditor from 'vditor';
import 'vditor/dist/index.css';
import { propTypes } from '/@/utils/propTypes';
import { useLocale } from '/@/hooks/web/useLocale';
type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined;
export default defineComponent({
emits: ['update:value'],
emits: ['change'],
props: {
height: propTypes.number.def(360),
value: propTypes.string.def(''),
......@@ -19,17 +30,42 @@
const vditorRef = ref<Nullable<Vditor>>(null);
const initedRef = ref(false);
const lang = ref<Lang>();
const { getLang } = useLocale();
const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => {
switch (unref(getLang)) {
case 'en':
lang.value = 'en_US';
break;
case 'ja':
lang.value = 'ja_JP';
break;
case 'ko':
lang.value = 'ko_KR';
break;
default:
lang.value = 'zh_CN';
}
return lang.value;
});
function init() {
const wrapEl = unref(wrapRef);
if (!wrapEl) return;
const bindValue = { ...attrs, ...props };
vditorRef.value = new Vditor(wrapEl, {
lang: unref(getCurrentLang),
mode: 'sv',
preview: {
actions: [],
},
input: (v) => {
emit('update:value', v);
// emit('update:value', v);
emit('change', v);
},
blur: () => {
unref(vditorRef)?.setValue(props.value);
},
...bindValue,
cache: {
......@@ -39,14 +75,20 @@
initedRef.value = true;
}
watchEffect(() => {
nextTick(() => {
const vditor = unref(vditorRef);
if (unref(initedRef) && props.value && vditor) {
vditor.setValue(props.value);
}
});
});
// watch(
// () => props.value,
// () => {
// nextTick(() => {
// const vditor = unref(vditorRef);
// if (unref(initedRef) && props.value && vditor) {
// vditor.setValue(props.value);
// }
// });
// },
// {
// immediate: true,
// }
// );
onMounted(() => {
nextTick(() => {
......
......@@ -5,7 +5,7 @@
import { withInstall } from '../util';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export const Scrollbar = createAsyncComponent(() => import('./src/Scrollbar'));
export const Scrollbar = createAsyncComponent(() => import('./src/index.vue'));
withInstall(Scrollbar);
......
import type { PropType } from 'vue';
import { renderThumbStyle, BAR_MAP } from './util';
import { defineComponent, computed, unref, inject, Ref, reactive, ref, onBeforeUnmount } from 'vue';
import { on, off } from '/@/utils/domUtils';
export default defineComponent({
name: 'Bar',
props: {
vertical: {
type: Boolean as PropType<boolean>,
default: false,
},
size: String as PropType<string>,
move: Number as PropType<number>,
},
setup(props) {
const thumbRef = ref<Nullable<HTMLDivElement>>(null);
const elRef = ref<Nullable<HTMLDivElement>>(null);
const commonState = reactive<Indexable>({});
const getBarRef = computed(() => {
return BAR_MAP[props.vertical ? 'vertical' : 'horizontal'];
});
const parentElRef = inject('scroll-bar-wrap') as Ref<Nullable<HTMLDivElement>>;
function clickThumbHandler(e: any) {
const { ctrlKey, button, currentTarget } = e;
// prevent click event of right button
if (ctrlKey || button === 2 || !currentTarget) {
return;
}
startDrag(e);
const bar = unref(getBarRef);
commonState[bar.axis] =
currentTarget[bar.offset] -
(e[bar.client as keyof typeof e] - currentTarget.getBoundingClientRect()[bar.direction]);
}
function clickTrackHandler(e: any) {
const bar = unref(getBarRef);
const offset = Math.abs(e.target.getBoundingClientRect()[bar.direction] - e[bar.client]);
const thumbEl = unref(thumbRef) as any;
const parentEl = unref(parentElRef) as any;
const el = unref(elRef) as any;
if (!thumbEl || !el || !parentEl) return;
const thumbHalf = thumbEl[bar.offset] / 2;
const thumbPositionPercentage = ((offset - thumbHalf) * 100) / el[bar.offset];
parentEl[bar.scroll] = (thumbPositionPercentage * parentEl[bar.scrollSize]) / 100;
}
function startDrag(e: Event) {
e.stopImmediatePropagation();
commonState.cursorDown = true;
on(document, 'mousemove', mouseMoveDocumentHandler);
on(document, 'mouseup', mouseUpDocumentHandler);
document.onselectstart = () => false;
}
function mouseMoveDocumentHandler(e: any) {
if (commonState.cursorDown === false) return;
const bar = unref(getBarRef);
const prevPage = commonState[bar.axis];
const el = unref(elRef) as any;
const parentEl = unref(parentElRef) as any;
const thumbEl = unref(thumbRef) as any;
if (!prevPage || !el || !thumbEl || !parentEl) return;
const rect = el.getBoundingClientRect() as any;
const offset = (rect[bar.direction] - e[bar.client]) * -1;
const thumbClickPosition = thumbEl[bar.offset] - prevPage;
const thumbPositionPercentage = ((offset - thumbClickPosition) * 100) / el[bar.offset];
parentEl[bar.scroll] = (thumbPositionPercentage * parentEl[bar.scrollSize]) / 100;
}
function mouseUpDocumentHandler() {
const bar = unref(getBarRef);
commonState.cursorDown = false;
commonState[bar.axis] = 0;
off(document, 'mousemove', mouseMoveDocumentHandler);
document.onselectstart = null;
}
onBeforeUnmount(() => {
off(document, 'mouseup', mouseUpDocumentHandler);
});
return () => {
const bar = unref(getBarRef);
const { size, move } = props;
return (
<div
class={['scrollbar__bar', 'is-' + bar.key]}
onMousedown={clickTrackHandler}
ref={elRef}
>
<div
ref={thumbRef}
class="scrollbar__thumb"
onMousedown={clickThumbHandler}
style={renderThumbStyle({ size, move, bar })}
/>
</div>
);
};
},
});
import { addResizeListener, removeResizeListener } from '/@/utils/event/resizeEvent';
import scrollbarWidth from '/@/utils/scrollbarWidth';
import { toObject } from './util';
import Bar from './Bar';
import { isString } from '/@/utils/is';
import {
defineComponent,
PropType,
unref,
reactive,
ref,
provide,
onMounted,
nextTick,
onBeforeUnmount,
} from 'vue';
import { getSlot } from '/@/utils/helper/tsxHelper';
import './index.less';
import { useExpose } from '/@/hooks/core/useExpose';
import { ScrollbarType } from './types';
export default defineComponent({
name: 'Scrollbar',
props: {
native: Boolean as PropType<boolean>,
wrapStyle: {
type: Object as PropType<any>,
},
wrapClass: { type: String as PropType<string>, required: false },
viewClass: { type: String as PropType<string> },
viewStyle: { type: Object as PropType<any> },
noresize: Boolean as PropType<boolean>,
tag: {
type: String as PropType<string>,
default: 'div',
},
},
setup(props, { slots }) {
const resizeRef = ref<Nullable<HTMLDivElement>>(null);
const wrapElRef = ref<Nullable<HTMLDivElement>>(null);
provide('scroll-bar-wrap', wrapElRef);
const state = reactive({
sizeWidth: '0',
sizeHeight: '0',
moveX: 0,
moveY: 0,
});
function handleScroll() {
const warpEl = unref(wrapElRef);
if (!warpEl) return;
const { scrollTop, scrollLeft, clientHeight, clientWidth } = warpEl;
state.moveY = (scrollTop * 100) / clientHeight;
state.moveX = (scrollLeft * 100) / clientWidth;
}
function update() {
const warpEl = unref(wrapElRef);
if (!warpEl) return;
const { scrollHeight, scrollWidth, clientHeight, clientWidth } = warpEl;
const heightPercentage = (clientHeight * 100) / scrollHeight;
const widthPercentage = (clientWidth * 100) / scrollWidth;
state.sizeHeight = heightPercentage < 100 ? heightPercentage + '%' : '';
state.sizeWidth = widthPercentage < 100 ? widthPercentage + '%' : '';
}
onMounted(() => {
useExpose<ScrollbarType>({
wrap: unref(wrapElRef),
});
const { native, noresize } = props;
const resizeEl = unref(resizeRef);
const warpEl = unref(wrapElRef);
if (native || !resizeEl || !warpEl) return;
nextTick(update);
if (!noresize) {
addResizeListener(resizeEl, update);
addResizeListener(warpEl, update);
}
});
onBeforeUnmount(() => {
const { native, noresize } = props;
const resizeEl = unref(resizeRef);
const warpEl = unref(wrapElRef);
if (native || !resizeEl || !warpEl) return;
if (!noresize) {
removeResizeListener(resizeEl, update);
removeResizeListener(warpEl, update);
}
});
return () => {
const { native, tag, viewClass, viewStyle, wrapClass, wrapStyle } = props;
let style: any = wrapStyle;
const gutter = scrollbarWidth();
if (gutter) {
const gutterWith = `-${gutter}px`;
const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`;
if (Array.isArray(wrapStyle)) {
style = toObject(wrapStyle);
style.marginRight = style.marginBottom = gutterWith;
} else if (isString(wrapStyle)) {
style += gutterStyle;
} else {
style = gutterStyle;
}
}
const Tag = tag as any;
const view = (
<Tag class={['scrollbar__view', viewClass]} style={viewStyle} ref={resizeRef}>
{getSlot(slots)}
</Tag>
);
const wrap = (
<div
ref={wrapElRef}
style={style}
onScroll={handleScroll}
class={[wrapClass, 'scrollbar__wrap', gutter ? '' : 'scrollbar__wrap--hidden-default']}
>
{[view]}
</div>
);
let nodes: any[] = [];
const { moveX, sizeWidth, moveY, sizeHeight } = state;
if (!native) {
nodes = [
wrap,
/* eslint-disable */
<Bar move={moveX} size={sizeWidth}></Bar>,
<Bar vertical move={moveY} size={sizeHeight}></Bar>,
];
} else {
nodes = [
<div ref="wrap" class={[wrapClass, 'scrollbar__wrap']} style={style}>
{[view]}
</div>,
];
}
return <div class="scrollbar">{nodes}</div>;
};
},
});
import {
defineComponent,
h,
computed,
ref,
getCurrentInstance,
onUnmounted,
inject,
Ref,
} from 'vue';
import { on, off } from '/@/utils/domUtils';
import { renderThumbStyle, BAR_MAP } from './util';
export default defineComponent({
name: 'Bar',
props: {
vertical: Boolean,
size: String,
move: Number,
},
setup(props) {
const instance = getCurrentInstance();
const thumb = ref<any>(null);
const wrap = inject('scroll-bar-wrap', {} as Ref<Nullable<HTMLElement>>) as any;
const bar = computed(() => {
return BAR_MAP[props.vertical ? 'vertical' : 'horizontal'];
});
const barStore = ref<Indexable>({});
const cursorDown = ref<any>(null);
const clickThumbHandler = (e: any) => {
// prevent click event of right button
if (e.ctrlKey || e.button === 2) {
return;
}
startDrag(e);
barStore.value[bar.value.axis] =
e.currentTarget[bar.value.offset] -
(e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]);
};
const clickTrackHandler = (e: any) => {
const offset = Math.abs(
e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]
);
const thumbHalf = thumb.value[bar.value.offset] / 2;
const thumbPositionPercentage =
((offset - thumbHalf) * 100) / instance?.vnode.el?.[bar.value.offset];
wrap.value[bar.value.scroll] =
(thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100;
};
const startDrag = (e: any) => {
e.stopImmediatePropagation();
cursorDown.value = true;
on(document, 'mousemove', mouseMoveDocumentHandler);
on(document, 'mouseup', mouseUpDocumentHandler);
document.onselectstart = () => false;
};
const mouseMoveDocumentHandler = (e: any) => {
if (cursorDown.value === false) return;
const prevPage = barStore.value[bar.value.axis];
if (!prevPage) return;
const offset =
(instance?.vnode.el?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) *
-1;
const thumbClickPosition = thumb.value[bar.value.offset] - prevPage;
const thumbPositionPercentage =
((offset - thumbClickPosition) * 100) / instance?.vnode.el?.[bar.value.offset];
wrap.value[bar.value.scroll] =
(thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100;
};
function mouseUpDocumentHandler() {
cursorDown.value = false;
barStore.value[bar.value.axis] = 0;
off(document, 'mousemove', mouseMoveDocumentHandler);
document.onselectstart = null;
}
onUnmounted(() => {
off(document, 'mouseup', mouseUpDocumentHandler);
});
return () =>
h(
'div',
{
class: ['scrollbar__bar', 'is-' + bar.value.key],
onMousedown: clickTrackHandler,
},
h('div', {
ref: thumb,
class: 'scrollbar__thumb',
onMousedown: clickThumbHandler,
style: renderThumbStyle({
size: props.size,
move: props.move,
bar: bar.value,
}),
})
);
},
});
.scrollbar {
position: relative;
overflow: hidden;
&__wrap {
height: 100%;
overflow: scroll;
&--hidden-default {
scrollbar-width: none;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
}
&__thumb {
position: relative;
display: block;
width: 0;
height: 0;
cursor: pointer;
background-color: rgba(144, 147, 153, 0.3);
border-radius: inherit;
transition: 0.3s background-color;
&:hover {
background-color: rgba(144, 147, 153, 0.5);
}
}
&__bar {
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1;
border-radius: 4px;
opacity: 0;
-webkit-transition: opacity 80ms ease;
transition: opacity 80ms ease;
&.is-vertical {
top: 2px;
width: 5px;
& > div {
width: 100%;
}
}
&.is-horizontal {
left: 2px;
height: 5px;
& > div {
height: 100%;
}
}
}
}
.scrollbar:active > .scrollbar__bar,
.scrollbar:focus > .scrollbar__bar,
.scrollbar:hover > .scrollbar__bar {
opacity: 1;
transition: opacity 180ms ease;
}
<template>
<div class="scrollbar">
<div
ref="wrap"
:class="[wrapClass, 'scrollbar__wrap', native ? '' : 'scrollbar__wrap--hidden-default']"
:style="style"
@scroll="handleScroll"
>
<component :is="tag" ref="resize" :class="['scrollbar__view', viewClass]" :style="viewStyle">
<slot></slot>
</component>
</div>
<template v-if="!native">
<bar :move="moveX" :size="sizeWidth" />
<bar vertical :move="moveY" :size="sizeHeight" />
</template>
</div>
</template>
<script lang="ts">
import { addResizeListener, removeResizeListener } from '/@/utils/event/resizeEvent';
import { toObject } from './util';
import {
defineComponent,
ref,
onMounted,
onBeforeUnmount,
nextTick,
provide,
computed,
} from 'vue';
import Bar from './bar';
export default defineComponent({
name: 'Scrollbar',
components: { Bar },
props: {
native: {
type: Boolean,
default: false,
},
wrapStyle: {
type: [String, Array],
default: '',
},
wrapClass: {
type: [String, Array],
default: '',
},
viewClass: {
type: [String, Array],
default: '',
},
viewStyle: {
type: [String, Array],
default: '',
},
noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
tag: {
type: String,
default: 'div',
},
},
setup(props) {
const sizeWidth = ref('0');
const sizeHeight = ref('0');
const moveX = ref(0);
const moveY = ref(0);
const wrap = ref<any>(null);
const resize = ref<any>(null);
provide('scroll-bar-wrap', wrap);
const handleScroll = () => {
if (!props.native) {
moveY.value = (wrap.value.scrollTop * 100) / wrap.value.clientHeight;
moveX.value = (wrap.value.scrollLeft * 100) / wrap.value.clientWidth;
}
};
const update = () => {
if (!wrap.value) return;
const heightPercentage = (wrap.value.clientHeight * 100) / wrap.value.scrollHeight;
const widthPercentage = (wrap.value.clientWidth * 100) / wrap.value.scrollWidth;
sizeHeight.value = heightPercentage < 100 ? heightPercentage + '%' : '';
sizeWidth.value = widthPercentage < 100 ? widthPercentage + '%' : '';
};
onMounted(() => {
if (props.native) return;
nextTick(update);
!props.noresize && addResizeListener(resize.value, update);
});
onBeforeUnmount(() => {
if (props.native) return;
!props.noresize && removeResizeListener(resize.value, update);
});
const style = computed(() => {
let style: any = props.wrapStyle;
if (Array.isArray(props.wrapStyle)) {
style = toObject(props.wrapStyle);
}
return style;
});
return {
moveX,
moveY,
sizeWidth,
sizeHeight,
style,
wrap,
resize,
update,
handleScroll,
};
},
});
</script>
<style lang="less">
.scrollbar {
position: relative;
height: 100%;
overflow: hidden;
&__wrap {
height: 100%;
overflow: scroll;
&--hidden-default {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
opacity: 0;
}
}
}
&__thumb {
position: relative;
display: block;
width: 0;
height: 0;
cursor: pointer;
background-color: rgba(144, 147, 153, 0.3);
border-radius: inherit;
transition: 0.3s background-color;
&:hover {
background-color: rgba(144, 147, 153, 0.5);
}
}
&__bar {
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1;
border-radius: 4px;
opacity: 0;
-webkit-transition: opacity 80ms ease;
transition: opacity 80ms ease;
&.is-vertical {
top: 2px;
width: 6px;
& > div {
width: 100%;
}
}
&.is-horizontal {
left: 2px;
height: 6px;
& > div {
height: 100%;
}
}
}
}
.scrollbar:active > .scrollbar__bar,
.scrollbar:focus > .scrollbar__bar,
.scrollbar:hover > .scrollbar__bar {
opacity: 1;
transition: opacity 340ms ease-out;
}
</style>
......@@ -59,6 +59,8 @@
if (el.scrollHeight !== 0) {
// for safari: add class after set height, or it will jump to zero height suddenly, weired
addClass(el, 'collapse-transition');
// in vue3.0.4, transitionProperty is set 'none' to avoid 'v-leave-from' issue
el.style.transitionProperty = 'height';
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
......
export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja';
export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko';
......@@ -180,6 +180,16 @@ const menu: MenuModule = {
{
path: 'markdown',
name: t('routes.demo.editor.markdown'),
children: [
{
path: 'index',
name: t('routes.demo.editor.tinymceBasic'),
},
{
path: 'editor',
name: t('routes.demo.editor.tinymceForm'),
},
],
},
{
path: 'tinymce',
......
......@@ -288,12 +288,32 @@ const comp: AppRouteModule = {
children: [
{
path: 'markdown',
component: getParentLayout('MarkdownDemo'),
name: 'MarkdownDemo',
component: () => import('/@/views/demo/editor/Markdown.vue'),
meta: {
title: t('routes.demo.editor.markdown'),
},
redirect: '/comp/editor/markdown/index',
children: [
{
path: 'index',
name: 'MarkDownBasicDemo',
component: () => import('/@/views/demo/editor/markdown/index.vue'),
meta: {
title: t('routes.demo.editor.tinymceBasic'),
},
},
{
path: 'editor',
name: 'MarkDownFormDemo',
component: () => import('/@/views/demo/editor/markdown/Editor.vue'),
meta: {
title: t('routes.demo.editor.tinymceForm'),
},
},
],
},
{
path: 'tinymce',
component: getParentLayout('TinymceDemo'),
......
<template>
<div class="m-4">
<CollapseContainer title="MarkDown表单">
<BasicForm
:labelWidth="100"
:schemas="schemas"
:actionColOptions="{ span: 24 }"
@submit="handleSubmit"
>
</BasicForm>
</CollapseContainer>
</div>
</template>
<script lang="ts">
import { defineComponent, h } from 'vue';
import { BasicForm, FormSchema } from '/@/components/Form/index';
import { CollapseContainer } from '/@/components/Container/index';
import { useMessage } from '/@/hooks/web/useMessage';
import { MarkDown } from '/@/components/Markdown';
const schemas: FormSchema[] = [
{
field: 'title',
component: 'Input',
label: 'title',
defaultValue: '标题',
rules: [{ required: true }],
},
{
field: 'markdown',
component: 'Input',
label: 'markdown',
defaultValue: 'defaultValue',
rules: [{ required: true, trigger: 'blur' }],
render: ({ model, field }) => {
return h(MarkDown, {
value: model[field],
onChange: (value: string) => {
model[field] = value;
},
});
},
},
];
export default defineComponent({
components: { BasicForm, CollapseContainer },
setup() {
const { createMessage } = useMessage();
return {
schemas,
handleSubmit: (values: any) => {
createMessage.success('click search,values:' + JSON.stringify(values));
},
};
},
});
</script>
<template>
<div class="p-4">
<a-button @click="toggleTheme" class="mb-2" type="primary">黑暗主题</a-button>
<MarkDown v-model:value="value" ref="markDownRef" />
<MarkDown :value="value" @change="handleChange" ref="markDownRef" />
</div>
</template>
<script lang="ts">
......@@ -23,10 +23,16 @@
const vditor = markDown.getVditor();
vditor.setTheme('dark');
}
function handleChange(v: string) {
valueRef.value = v;
}
return {
value: valueRef,
toggleTheme,
markDownRef,
handleChange,
};
},
});
......
......@@ -15,16 +15,11 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"experimentalDecorators": true,
"lib": [
"dom",
"esnext"
],
"lib": ["dom", "esnext"],
"incremental": true,
"skipLibCheck": true,
"paths": {
"/@/*": [
"src/*"
]
"/@/*": ["src/*"]
}
},
"plugins": [
......@@ -32,15 +27,6 @@
"name": "@vuedx/typescript-plugin-vue"
}
],
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"exclude": [
"node_modules",
"dist",
"**/*.js"
]
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist", "**/*.js"]
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册