Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
P
poc-fe
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
poc
poc-fe
Commits
6feba3da
Commit
6feba3da
authored
Apr 21, 2025
by
tyyin lan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 编辑器指令优化编辑
parent
28db76d1
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
516 additions
and
72 deletions
+516
-72
index.html
index.html
+1
-1
content-optimization-edit.vue
src/components/custom-editor/content-optimization-edit.vue
+315
-0
custom-editor.vue
src/components/custom-editor/custom-editor.vue
+65
-71
fetch-event-stream-source.ts
...mponents/custom-editor/utils/fetch-event-stream-source.ts
+83
-0
en.yaml
src/locales/langs/en.yaml
+13
-0
zh-cn.yaml
src/locales/langs/zh-cn.yaml
+13
-0
zh-hk.yaml
src/locales/langs/zh-hk.yaml
+13
-0
locales.d.ts
types/locales.d.ts
+13
-0
No files found.
index.html
View file @
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"
...
...
src/components/custom-editor/content-optimization-edit.vue
0 → 100644
View file @
6feba3da
This diff is collapsed.
Click to expand it.
src/components/custom-editor/custom-editor.vue
View file @
6feba3da
<
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,11 +181,15 @@ function getContent() {
...
@@ -185,11 +181,15 @@ function getContent() {
}
}
function
onDownloadFile
()
{
function
onDownloadFile
()
{
asBlob
(
getContent
()).
then
((
data
)
=>
{
const
content
=
getContent
()
downloadFile
(
data
as
Blob
).
then
(()
=>
{
fetchExportFile
<
string
>
(
'doc'
,
content
).
then
((
res
)
=>
{
if
(
res
.
code
!==
0
)
return
downloadFile
(
res
.
data
)
window
.
$message
.
success
(
t
(
'common_module.download_success'
))
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"
/>
...
...
src/components/custom-editor/utils/fetch-event-stream-source.ts
0 → 100644
View file @
6feba3da
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
}
src/locales/langs/en.yaml
View file @
6feba3da
...
@@ -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'
src/locales/langs/zh-cn.yaml
View file @
6feba3da
...
@@ -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
:
'
文采优美'
src/locales/langs/zh-hk.yaml
View file @
6feba3da
...
@@ -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
:
'
文采優美'
types/locales.d.ts
View file @
6feba3da
...
@@ -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
}
}
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment