feat:新增富文本编辑器组件

environments/test/deployments/1
向文可 4 years ago
parent facb4104d3
commit e3e3b23a4e

@ -15,11 +15,13 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons": "^0.0.11", "@element-plus/icons": "^0.0.11",
"@vueup/vue-quill": "^1.0.0-beta.8",
"axios": "^0.26.1", "axios": "^0.26.1",
"dayjs": "^1.11.0", "dayjs": "^1.11.0",
"element-plus": "^2.1.2", "element-plus": "^2.1.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"qs": "^6.10.3", "qs": "^6.10.3",
"quill-image-uploader": "^1.2.2",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"vue": "^3.2.25", "vue": "^3.2.25",
"vue-router": "^4.0.14", "vue-router": "^4.0.14",

@ -0,0 +1,10 @@
import request from '@/utils/request';
// 上传文件
export function upload(data) {
return request({
url: '/ks-admin/local/upload/file',
method: 'POST',
data,
});
}

@ -0,0 +1,165 @@
<template>
<component :is="render"></component>
</template>
<script lang="jsx">
export default defineComponent({
inheritAttrs: false,
});
</script>
<script setup lang="jsx">
import { upload } from '@/api/file';
import { Quill, QuillEditor } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import ImageUploader from 'quill-image-uploader';
Quill.register('modules/imageUploader', ImageUploader);
const props = defineProps({
readonly: {
type: Boolean,
default: false,
},
preview: {
type: [Boolean, String],
default: 'app',
},
});
const attrs = useAttrs();
const emits = defineEmits(['update:modelValue', 'change']);
const editor = ref(null);
const options = {
bounds: '.el-editor',
debug: 'warn',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
['link', 'image'],
[{ header: 1 }, { header: 2 }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ direction: 'rtl' }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
[{ align: [] }],
['clean'],
],
imageUploader: {
upload: (file) => {
return new Promise(async (resolve, reject) => {
const formdata = new FormData();
formdata.append('file', file);
const url = await upload(formdata);
if (url) {
resolve(url);
} else {
reject('上传失败');
}
});
},
},
},
placeholder: '请输入内容...',
readOnly: props.readonly,
theme: 'snow',
};
const content = ref(null);
watch(
() => content,
(value, old) => {
emits('update:modelValue', unref(value));
emits('change', unref(value), unref(old));
},
{ deep: true }
);
const handleReady = (e) => {
unref(editor).setHTML(attrs.modelValue || '');
};
const handleUpdateContent = (value) => {
content.value = unref(editor).getHTML();
};
const preview = ref(props.preview === false ? false : typeof props.preview === 'string' ? props.preview : 'app');
const handlePreview = (mode) => {
preview.value = mode;
};
const render = () => (
<div class="el-editor">
<div class="editor">
<QuillEditor
ref={editor}
options={options}
{...attrs}
content={unref(content)}
on={{
'update:content': handleUpdateContent,
ready: handleReady,
}}
/>
</div>
{unref(preview) ? (
<div class={{ preview: true, ['--' + unref(preview)]: true, 'ql-snow': true }}>
<h3 class="header">
<div
class={{ btn: true, active: unref(preview) === 'app' }}
onClick={() => handlePreview('app')}
>
APP预览
</div>
<div class={{ btn: true, active: unref(preview) === 'pc' }} onClick={() => handlePreview('pc')}>
PC预览
</div>
</h3>
<div class="content ql-editor" v-html={unref(content)}></div>
</div>
) : (
''
)}
</div>
);
</script>
<style lang="less" scoped>
.el-editor {
width: 100%;
height: 480px;
display: flex;
:deep(.editor) {
display: flex;
flex-direction: column;
flex: 1;
}
:deep(.preview) {
height: 100%;
margin-left: 20px;
border: 1px solid #d1d5db;
&.--app {
width: 320px;
}
&.--pc {
width: 640px;
}
.header {
padding: 10px;
border-bottom: 1px solid #d1d5db;
display: flex;
.btn {
color: #d1d5db;
cursor: pointer;
&.active {
color: #000;
}
+ .btn {
margin-left: 10px;
}
}
}
.content {
padding: 10px;
}
}
}
</style>

@ -2,9 +2,8 @@
<component :is="render" /> <component :is="render" />
</template> </template>
<script setup lang="jsx"> <script setup lang="jsx">
import SortableTable from './SortableTable.vue'; import ElTable from './extra/ElTable.vue';
import { ElTable, ElTableColumn } from 'element-plus/es/components/table/index'; import { ElTableColumn } from 'element-plus/es/components/table/index';
import 'element-plus/es/components/table/style/css';
const props = defineProps({ const props = defineProps({
code: { code: {
type: String, type: String,
@ -321,7 +320,6 @@
doLayout, doLayout,
sort, sort,
}); });
const Component = props.sortable ? SortableTable : ElTable;
const render = () => ( const render = () => (
<div class="common-list"> <div class="common-list">
{slots.search ? <div class="search-box">{slots.search()}</div> : ''} {slots.search ? <div class="search-box">{slots.search()}</div> : ''}
@ -516,8 +514,9 @@
</ElDialog> </ElDialog>
</div> </div>
<div class="content-box"> <div class="content-box">
<Component <ElTable
ref={refTable} ref={refTable}
sortable={props.sortable}
border border
stripe stripe
highlightCurrentRow={false} highlightCurrentRow={false}
@ -549,7 +548,7 @@
'' ''
) )
)} )}
</Component> </ElTable>
</div> </div>
<div class="pagination-box"> <div class="pagination-box">
{props.config.page === false ? ( {props.config.page === false ? (

@ -69,7 +69,7 @@ import config from '@/configs';
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes: config.useLocalRouter ? routes : [], routes: config.useLocalRouter ? routes : globalRoutes,
}); });
import store from '@/store'; import store from '@/store';

@ -11,8 +11,9 @@
</ul> </ul>
<p>{{ list.map((item) => item.id) }}</p> <p>{{ list.map((item) => item.id) }}</p>
<p>{{ list.find((item) => item.id === '1').childList.map((item) => item.id) }}</p> <p>{{ list.find((item) => item.id === '1').childList.map((item) => item.id) }}</p>
<SortableTable <ElTable
v-loading="listLoading" v-loading="listLoading"
sortable
border border
code="parent" code="parent"
:data="list" :data="list"
@ -25,8 +26,9 @@
> >
<el-table-column label="展开" type="expand" width="60"> <el-table-column label="展开" type="expand" width="60">
<template #default="{ row }"> <template #default="{ row }">
<SortableTable <ElTable
v-loading="listLoading" v-loading="listLoading"
sortable
border border
code="child" code="child"
:data="row.childList || []" :data="row.childList || []"
@ -47,7 +49,7 @@
<template #empty> <template #empty>
<el-empty class="vab-data-empty" description="暂无数据" /> <el-empty class="vab-data-empty" description="暂无数据" />
</template> </template>
</SortableTable> </ElTable>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="排序" prop="sort" width="60" /> <el-table-column align="center" label="排序" prop="sort" width="60" />
@ -60,7 +62,7 @@
<template #empty> <template #empty>
<el-empty class="vab-data-empty" description="暂无数据" /> <el-empty class="vab-data-empty" description="暂无数据" />
</template> </template>
</SortableTable> </ElTable>
</div> </div>
</template> </template>

@ -15,6 +15,7 @@
<el-date-picker></el-date-picker> <el-date-picker></el-date-picker>
{{ form }} {{ form }}
<el-input v-model="form.msg" /> <el-input v-model="form.msg" />
<el-editor preview="pc" v-model="form.msg"></el-editor>
</div> </div>
</template> </template>

@ -44,7 +44,9 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
}, },
plugins: [ plugins: [
vue(), vue(),
vueJsx(), vueJsx({
transformOn: true,
}),
createSvgIconsPlugin({ createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/icons/svg')], iconDirs: [resolve(process.cwd(), 'src/icons/svg')],
symbolId: 'icon-[dir]-[name]', symbolId: 'icon-[dir]-[name]',

Loading…
Cancel
Save