This commit is contained in:
AN
2026-01-05 14:50:19 +08:00
6 changed files with 684 additions and 2755 deletions
+2 -1
View File
@@ -66,8 +66,9 @@
"@sa/materials": "workspace:*",
"@sa/utils": "workspace:*",
"@types/streamsaver": "^2.0.5",
"@umoteam/editor": "^9.0.1",
"@vueuse/core": "14.1.0",
"@wangeditor-next/editor": "^5.6.49",
"@wangeditor-next/editor-for-vue": "^5.1.14",
"clipboard": "2.0.11",
"dayjs": "1.11.19",
"defu": "6.1.4",
+581 -2641
View File
File diff suppressed because it is too large Load Diff
-104
View File
@@ -1,104 +0,0 @@
<script lang="ts" setup>
import { nextTick, ref, useAttrs, watch } from 'vue';
import type { UmoEditorOptions } from '@umoteam/editor';
import { UmoEditor } from '@umoteam/editor';
import { fetchBatchDeleteOss, fetchUploadFile } from '@/service/api/system/oss';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
defineOptions({
name: 'UmoDocEditor'
});
const attrs: UmoEditorOptions = useAttrs();
const appStore = useAppStore();
const themeStore = useThemeStore();
const umoEditorRef = ref<InstanceType<typeof UmoEditor>>();
const isSave = ref(false);
const value = defineModel<string>('value', { required: true, default: '' });
watch(
value,
() => {
nextTick(() => {
if (isSave.value) {
isSave.value = false;
return;
}
umoEditorRef.value?.setContent(value.value);
});
},
{
immediate: true
}
);
watch(
() => appStore.locale,
() => {
umoEditorRef.value?.setLocale(appStore.locale);
}
);
async function handleSave(content: { html: string }) {
isSave.value = true;
value.value = content.html;
return true;
}
async function handleFileUpload(file: File) {
const { error, data } = await fetchUploadFile(file);
if (error) throw new Error(error.message || '上传失败');
return {
id: data.ossId,
url: data.url
};
}
function handleFileDelete(id: CommonType.IdType) {
window.$dialog?.warning({
title: '确认删除文件?',
content: '文件删除后不可恢复,请确认是否删除!',
positiveText: '确认删除',
negativeText: '取消',
onPositiveClick: async () => {
const { error } = await fetchBatchDeleteOss([id]);
if (error) throw new Error(error.message || '文件删除失败');
}
});
return true;
}
defineExpose({
saveContent: () => umoEditorRef.value?.saveContent()
});
</script>
<template>
<div class="umo-editor size-full">
<UmoEditor
v-bind="attrs"
ref="umoEditorRef"
:theme="themeStore.darkMode ? 'dark' : 'light'"
@save="handleSave"
@file-upload="handleFileUpload"
@file-delete="handleFileDelete"
/>
</div>
</template>
<style>
body .flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.umo-editor .flex-center {
display: inherit !important;
align-items: inherit !important;
justify-content: inherit !important;
}
</style>
+96
View File
@@ -0,0 +1,96 @@
<script setup lang="ts">
import { onBeforeUnmount, shallowRef, watch } from 'vue';
import '@wangeditor-next/editor/dist/css/style.css';
import { Editor, Toolbar } from '@wangeditor-next/editor-for-vue';
import { i18nChangeLanguage } from '@wangeditor-next/editor';
import type { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor-next/editor';
import { useAppStore } from '@/store/modules/app';
import { getToken } from '@/store/modules/auth/shared';
import { getServiceBaseURL } from '@/utils/service';
defineOptions({
name: 'WangEditor'
});
const appStore = useAppStore();
const editorRef = shallowRef<IDomEditor>();
const value = defineModel<string>('value', { required: true, default: '' });
type InsertFnType = (url: string, alt?: string, href?: string) => void;
const toolbarConfig: Partial<IToolbarConfig> = {};
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
const editorConfig: Partial<IEditorConfig> = {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
server: `${baseURL}/resource/oss/upload`,
fieldName: 'file',
meta: {},
headers: {
// @ts-expect-error ignore this type error
Authorization: `Bearer ${getToken()}`,
clientid: import.meta.env.VITE_APP_CLIENT_ID!
},
metaWithUrl: false,
allowedFileTypes: ['image/*'],
customInsert(res: any, insertFn: InsertFnType) {
if (String(res?.code) !== '200') {
window.$message?.error(res?.msg || res);
return;
}
insertFn(res.data?.url, res.data?.fieldName);
},
onSuccess() {},
onFailed() {},
onError() {}
}
}
};
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor;
};
watch(
() => appStore.locale,
() => {
const localeMap = {
'zh-CN': 'zh-CN',
'en-US': 'en'
};
i18nChangeLanguage(localeMap[appStore.locale]);
}
);
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor === null) return;
editorRef.value?.destroy();
});
</script>
<template>
<NCard size="small" class="size-full min-h-500px" content-class="size-full">
<Toolbar :editor="editorRef" :default-config="toolbarConfig" class="border-b-1px border-gray-200" />
<Editor v-model="value" :default-config="editorConfig" @on-created="handleCreated" />
</NCard>
</template>
<style scoped>
:deep(.w-e-toolbar) {
background: inherit !important;
border-color: #999 !important;
}
:deep(.w-e-text-container) {
background: inherit;
border-color: #999 !important;
}
</style>
+2 -4
View File
@@ -16,7 +16,6 @@ declare module 'vue' {
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
BooleanTag: typeof import('./../components/custom/boolean-tag.vue')['default']
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
copy: typeof import('./../components/custom/dict-radio copy.vue')['default']
CountTo: typeof import('./../components/custom/count-to.vue')['default']
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
DataTable: typeof import('./../components/common/data-table.vue')['default']
@@ -154,8 +153,8 @@ declare module 'vue' {
TableSiderLayout: typeof import('./../components/advanced/table-sider-layout.vue')['default']
TenantSelect: typeof import('./../components/custom/tenant-select.vue')['default']
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
UmoDocEditor: typeof import('./../components/custom/umo-doc-editor.vue')['default']
UserSelect: typeof import('./../components/custom/user-select.vue')['default']
WangEditor: typeof import('./../components/custom/wang-editor.vue')['default']
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
}
}
@@ -166,7 +165,6 @@ declare global {
const BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
const BooleanTag: typeof import('./../components/custom/boolean-tag.vue')['default']
const ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
const copy: typeof import('./../components/custom/dict-radio copy.vue')['default']
const CountTo: typeof import('./../components/custom/count-to.vue')['default']
const DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
const DataTable: typeof import('./../components/common/data-table.vue')['default']
@@ -304,7 +302,7 @@ declare global {
const TableSiderLayout: typeof import('./../components/advanced/table-sider-layout.vue')['default']
const TenantSelect: typeof import('./../components/custom/tenant-select.vue')['default']
const ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
const UmoDocEditor: typeof import('./../components/custom/umo-doc-editor.vue')['default']
const UserSelect: typeof import('./../components/custom/user-select.vue')['default']
const WangEditor: typeof import('./../components/custom/wang-editor.vue')['default']
const WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
}
@@ -1,7 +1,6 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { jsonClone } from '@sa/utils';
import type { UmoEditor } from '@umoteam/editor';
import { fetchCreateNotice, fetchUpdateNotice } from '@/service/api/system/notice';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
@@ -29,7 +28,6 @@ const visible = defineModel<boolean>('visible', {
default: false
});
const umoEditorRef = ref<InstanceType<typeof UmoEditor>>();
const { formRef, validate, restoreValidation } = useNaiveForm();
const { createRequiredRule } = useFormRules();
@@ -77,7 +75,7 @@ function closeDrawer() {
}
async function handleSubmit() {
umoEditorRef.value?.saveContent();
// umoEditorRef.value?.saveContent();
await validate();
const { noticeId, noticeTitle, noticeType, noticeContent, status } = model.value;
@@ -112,7 +110,7 @@ watch(visible, () => {
:trap-focus="false"
:title="title"
display-directive="show"
:width="1000"
:width="1500"
class="max-w-90%"
>
<NDrawerContent :title="title" :native-scrollbar="false" closable>
@@ -129,7 +127,7 @@ watch(visible, () => {
</NFormItem>
</div>
<NFormItem :show-label="false" path="noticeContent">
<UmoDocEditor ref="umoEditorRef" v-model:value="model.noticeContent!" />
<WangEditor v-model:value="model.noticeContent!" />
</NFormItem>
</NForm>
<template #footer>