Commit 863398a8 authored by tyyin lan's avatar tyyin lan

Merge branch 'dev'

parents 164941f0 6feba3da
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/> />
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_4711453_usul8q5bwqp.css" /> <link rel="stylesheet" href="//at.alicdn.com/t/c/font_4711453_lz4oxe9et7.css" />
<link <link
rel="preload" rel="preload"
href="https://gsst-poe-sit.gz.bcebos.com/front/SourceHanSansCN-Medium.otf" href="https://gsst-poe-sit.gz.bcebos.com/front/SourceHanSansCN-Medium.otf"
......
This diff is collapsed.
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue' import { computed, nextTick, onMounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
import { createEditorConfig } from './config/editor-config' import { createEditorConfig } from './config/editor-config'
import EditorToolbar from './editor-toolbar.vue' import EditorToolbar from './editor-toolbar.vue'
import { markdownTransformHtml } from '@/utils/markdown-parse'
import { asBlob } from 'html-docx-js-typescript'
import { downloadFile } from '@/utils/download-file' import { downloadFile } from '@/utils/download-file'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { fetchExportFile } from '@/apis/file'
import ContentOptimizationEdit from './content-optimization-edit.vue'
import { useElementSize } from '@vueuse/core'
import type { Editor } from 'tinymce'
interface Props { interface Props {
content: string content: string
...@@ -25,29 +27,23 @@ const emit = defineEmits<{ ...@@ -25,29 +27,23 @@ const emit = defineEmits<{
}>() }>()
defineExpose({ defineExpose({
getWordCount,
getContent, getContent,
}) })
const editorToolbarRef = useTemplateRef<InstanceType<typeof EditorToolbar>>('editorToolbarRef') const editorToolbarRef = useTemplateRef<InstanceType<typeof EditorToolbar>>('editorToolbarRef')
const { t } = useI18n() const { t } = useI18n()
const editorWrapperRef = useTemplateRef('editorWrapperRef')
let editor: any = {} const { height: editorWrapperRefHeight } = useElementSize(editorWrapperRef)
let controller: AbortController | null = null
const editorConfig = createEditorConfig() const editorConfig = createEditorConfig()
const editor = shallowRef<Editor | null>(null)
const isShowEditor = ref(false) const isShowEditor = ref(false)
const editorWrapperRef = ref<HTMLDivElement | null>(null)
const modifyInputContainerStyle = ref({
top: '0px',
})
const articleModifyInputContent = ref('')
const articleContentModifyContainerShow = ref(false) const articleContentModifyContainerShow = ref(false)
const articleContentModifyState = ref<'selection' | 'loading' | 'generating' | 'done'>('selection')
const articleContentModifyResponseText = ref('') const contentOptimizationEditContainerIsSetBottom = ref(false)
const articleContentModifyResponseTextResource = ref('') const contentOptimizationEditContainerLocation = ref(0)
const articleContentModifyContainerClientY = ref(0)
const editorContent = computed({ const editorContent = computed({
get() { get() {
...@@ -58,20 +54,17 @@ const editorContent = computed({ ...@@ -58,20 +54,17 @@ const editorContent = computed({
}, },
}) })
// const editorTitleText = ref('')
watch(articleContentModifyResponseTextResource, (newVal) => {
if (newVal)
articleContentModifyResponseText.value = markdownTransformHtml(
editor.dom ? editor.dom.decode(newVal) : newVal,
) as string
})
watch( watch(
() => props.content, () => props.content,
(val: string, prevVal: string) => { (val: string, prevVal: string) => {
if (editor && editor.getContent && typeof val === 'string' && val !== prevVal && val !== editor.getContent()) if (
editor.setContent(val) editor.value &&
editor.value.getContent &&
typeof val === 'string' &&
val !== prevVal &&
val !== editor.value.getContent()
)
editor.value.setContent(val)
}, },
) )
...@@ -80,7 +73,7 @@ onMounted(() => { ...@@ -80,7 +73,7 @@ onMounted(() => {
...editorConfig, ...editorConfig,
init_instance_callback: (editorInstance) => { init_instance_callback: (editorInstance) => {
isShowEditor.value = true isShowEditor.value = true
editor = editorInstance editor.value = editorInstance
nextTick(() => { nextTick(() => {
props.content && editorInstance.setContent(props.content) props.content && editorInstance.setContent(props.content)
...@@ -111,19 +104,8 @@ onMounted(() => { ...@@ -111,19 +104,8 @@ onMounted(() => {
editorInstance.on('mousedown', (e: MouseEvent) => { editorInstance.on('mousedown', (e: MouseEvent) => {
if (e.button !== 0) return if (e.button !== 0) return
articleContentModifyContainerClientY.value = e.clientY
if (articleContentModifyContainerShow.value) { if (articleContentModifyContainerShow.value) {
if (controller) {
controller.abort()
controller = null
}
articleContentModifyContainerShow.value = false articleContentModifyContainerShow.value = false
articleContentModifyResponseText.value = ''
articleContentModifyResponseTextResource.value = ''
articleContentModifyState.value = 'selection'
articleModifyInputContent.value = ''
} }
editorInstance.selection.collapse() editorInstance.selection.collapse()
...@@ -135,21 +117,35 @@ onMounted(() => { ...@@ -135,21 +117,35 @@ onMounted(() => {
const selectionContent = editorInstance.selection.getContent() const selectionContent = editorInstance.selection.getContent()
if (selectionContent) { if (selectionContent) {
const top = const rng = editorInstance.selection.getRng()
(e.clientY > articleContentModifyContainerClientY.value
? e.clientY const rects = rng.getClientRects()
: articleContentModifyContainerClientY.value) + 130 const lastRect = rects[rects.length - 1]
const bodyHeight = document.body.offsetHeight if (lastRect) {
/* 其中 46 是当前编辑器文档HTMl距离 外部挂载容器之间产生的高度 */
modifyInputContainerStyle.value.top = `${top > bodyHeight - 400 ? bodyHeight - 400 : top}px` const locationHeight = lastRect.bottom + 46 + 10
if (editorWrapperRefHeight.value - 260 < locationHeight) {
contentOptimizationEditContainerIsSetBottom.value = true
contentOptimizationEditContainerLocation.value =
editorWrapperRefHeight.value - (rects[0].bottom + 46 + 10) + rects[0].height + 16
} else {
contentOptimizationEditContainerIsSetBottom.value = false
contentOptimizationEditContainerLocation.value = locationHeight
}
}
articleContentModifyContainerShow.value = true articleContentModifyContainerShow.value = true
} }
}) })
editorInstance.on('keydown', () => { editorInstance.on('keyup', () => {
if (articleContentModifyContainerShow.value) articleContentModifyContainerShow.value = false const selectionContent = editorInstance.selection.getContent()
if (!selectionContent && articleContentModifyContainerShow.value) {
articleContentModifyContainerShow.value = false
}
}) })
}, },
}) })
...@@ -161,16 +157,16 @@ onMounted(() => { ...@@ -161,16 +157,16 @@ onMounted(() => {
// return !value.startsWith(' ') && !value.endsWith(' ') // return !value.startsWith(' ') && !value.endsWith(' ')
// } // }
function getWordCount() { // function getWordCount() {
const wordcount = window.tinymce.activeEditor!.plugins.wordcount // const wordcount = window.tinymce.activeEditor!.plugins.wordcount
return wordcount.body.getWordCount() // return wordcount.body.getWordCount()
} // }
function getContent() { function getContent() {
if (!isShowEditor.value) return '' if (!isShowEditor.value) return ''
const content = editor.getContent() const content = editor.value?.getContent() || ''
const parser = window.tinymce.html.DomParser({ validate: false }) const parser = window.tinymce.html.DomParser({ validate: false })
parser.addNodeFilter('mf-confirmation-box', (nodes) => { parser.addNodeFilter('mf-confirmation-box', (nodes) => {
...@@ -185,10 +181,14 @@ function getContent() { ...@@ -185,10 +181,14 @@ function getContent() {
} }
function onDownloadFile() { function onDownloadFile() {
asBlob(getContent()).then((data) => { const content = getContent()
downloadFile(data as Blob).then(() => {
window.$message.success(t('common_module.download_success')) fetchExportFile<string>('doc', content).then((res) => {
}) if (res.code !== 0) return
downloadFile(res.data)
window.$message.success(t('common_module.download_success'))
}) })
} }
</script> </script>
...@@ -203,25 +203,19 @@ function onDownloadFile() { ...@@ -203,25 +203,19 @@ function onDownloadFile() {
<EditorToolbar ref="editorToolbarRef" @download-file="onDownloadFile" /> <EditorToolbar ref="editorToolbarRef" @download-file="onDownloadFile" />
</div> </div>
<!-- <div class="doc-title px-4 py-2">
<NInput
v-model:value="editorTitleText"
type="textarea"
:allow-input="editorTitleTextVerify"
size="large"
class="font-semibold"
:autosize="{ minRows: 1 }"
:maxlength="50"
show-count
placeholder="请输入标题..."
/>
</div> -->
<div class="h-[10px]"></div> <div class="h-[10px]"></div>
<div class="grow"> <div class="grow">
<textarea class="tinymce-body" /> <textarea class="tinymce-body" />
</div> </div>
</div> </div>
<ContentOptimizationEdit
v-model:is-show-modal="articleContentModifyContainerShow"
:editor="editor"
:is-set-bottom="contentOptimizationEditContainerIsSetBottom"
:location="contentOptimizationEditContainerLocation"
/>
</div> </div>
<div v-show="!isShowEditor" class="flex h-full w-full items-center justify-center"> <div v-show="!isShowEditor" class="flex h-full w-full items-center justify-center">
<n-spin size="large" /> <n-spin size="large" />
......
import { BASE_URLS } from '@/config/base-url'
import { useSystemLanguageStore } from '@/store/modules/system-language'
import { useUserStore } from '@/store/modules/user'
import { languageKeyTransform } from '@/utils/language-key-transform'
import { fetchEventSource } from '@microsoft/fetch-event-source'
export interface ResponseData {
message: string
reasoningContent: string
function: { name: string }
}
interface Options {
onopen?: (response?: Response) => Promise<void>
onResponse?: (data: ResponseData) => void
onend?: () => void
onclose?: () => void
onerror?: (err: Error) => void
}
export default function fetchEventStreamSource(
url: string,
payload: object = {},
options: Options = {
onopen: async (_response) => {},
onResponse: (_data: ResponseData) => {},
onend: () => {},
onclose: () => {},
onerror: (_err: Error) => {},
},
) {
const ENV = import.meta.env.VITE_APP_ENV
const userStore = useUserStore()
const systemLanguageStore = useSystemLanguageStore()
const controller = new AbortController()
fetchEventSource(`${BASE_URLS[ENV || 'DEV']}/api/rest${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-request-token': userStore.token,
'x-lang': languageKeyTransform(systemLanguageStore.currentLanguageInfo.key),
},
body: JSON.stringify(payload),
signal: controller?.signal,
onopen: async (response) => {
return options.onopen && options.onopen(response)
},
onmessage: (e: { data: string }) => {
if (e.data === '[DONE]') {
options.onend && options.onend()
return
}
try {
const data = JSON.parse(e.data)
if (data.code === 0 || data.code === '0') {
data && options.onResponse && options.onResponse(data)
} else {
options.onerror && options.onerror(new Error(data.message))
controller.abort()
options.onclose && options.onclose()
}
} catch (error) {
options.onerror && options.onerror(error as Error)
}
},
onclose: () => {
options.onclose && options.onclose()
},
onerror: (err) => {
options.onerror && options.onerror(err)
throw err
},
})
return controller
}
...@@ -799,3 +799,16 @@ editor_module: ...@@ -799,3 +799,16 @@ editor_module:
center_align: 'Center align' center_align: 'Center align'
justify_right: 'Justify right' justify_right: 'Justify right'
align_both_ends: 'Align both ends' align_both_ends: 'Align both ends'
optimize_input_placeholder: 'Please enter the instructions for optimizing the text'
retouching: 'Retouching'
expansion: 'Expansion'
abbreviation: 'Abbreviation'
adjust_the_tone: 'Adjust the tone'
colloquial: 'Colloquial'
academicization: 'Academicization'
humorous_and_vivid: 'Humorous and vivid'
serious_and_formal: 'Serious and formal'
concise_and_clear: 'Concise and clear'
elegant_literary_style: 'Elegant literary style'
...@@ -798,3 +798,16 @@ editor_module: ...@@ -798,3 +798,16 @@ editor_module:
center_align: '居中对齐' center_align: '居中对齐'
justify_right: '右对齐' justify_right: '右对齐'
align_both_ends: '两端对齐' align_both_ends: '两端对齐'
optimize_input_placeholder: '请输入优化文本的指令'
retouching: '润色'
expansion: '扩写'
abbreviation: '缩写'
adjust_the_tone: '调整语气'
colloquial: '口语化'
academicization: '学术化'
humorous_and_vivid: '幽默生动'
serious_and_formal: '严肃正式'
concise_and_clear: '简洁明了'
elegant_literary_style: '文采优美'
...@@ -797,3 +797,16 @@ editor_module: ...@@ -797,3 +797,16 @@ editor_module:
center_align: '居中對齊' center_align: '居中對齊'
justify_right: '右對齊' justify_right: '右對齊'
align_both_ends: '兩端對齊' align_both_ends: '兩端對齊'
optimize_input_placeholder: '請輸入優化文本的指令'
retouching: '潤色'
expansion: '擴寫'
abbreviation: '縮寫'
adjust_the_tone: '調整語氣'
colloquial: '口語化'
academicization: '學術化'
humorous_and_vivid: '幽默生動'
serious_and_formal: '嚴肅正式'
concise_and_clear: '簡潔明了'
elegant_literary_style: '文采優美'
...@@ -820,6 +820,19 @@ declare namespace I18n { ...@@ -820,6 +820,19 @@ declare namespace I18n {
center_align: string center_align: string
justify_right: string justify_right: string
align_both_ends: string align_both_ends: string
optimize_input_placeholder: string
retouching: string
expansion: string
abbreviation: string
adjust_the_tone: string
colloquial: string
academicization: string
humorous_and_vivid: string
serious_and_formal: string
concise_and_clear: string
elegant_literary_style: string
} }
} }
} }
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