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
ad43b680
Commit
ad43b680
authored
Apr 20, 2025
by
TerryLan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: 编辑器内容优化弹窗完善
parent
d28e0366
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
296 additions
and
48 deletions
+296
-48
index.html
index.html
+1
-1
content-optimization-edit.vue
src/components/custom-editor/content-optimization-edit.vue
+166
-14
custom-editor.vue
src/components/custom-editor/custom-editor.vue
+39
-31
fetch-event-stream-source.ts
...mponents/custom-editor/utils/fetch-event-stream-source.ts
+78
-0
editor-drawer.vue
src/components/editor-drawer/editor-drawer.vue
+1
-1
base-url.ts
src/config/base-url.ts
+2
-1
vite.config.ts
vite.config.ts
+9
-0
No files found.
index.html
View file @
ad43b680
...
...
@@ -8,7 +8,7 @@
name=
"viewport"
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
rel=
"preload"
href=
"https://gsst-poe-sit.gz.bcebos.com/front/SourceHanSansCN-Medium.otf"
...
...
src/components/custom-editor/content-optimization-edit.vue
View file @
ad43b680
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
'vue'
import
{
ref
,
watchEffect
}
from
'vue'
import
fetchEventStreamSource
from
'./utils/fetch-event-stream-source'
import
type
{
Editor
}
from
'tinymce'
interface
Props
{
containerTop
:
number
editor
:
Editor
|
null
isSetBottom
:
boolean
location
:
number
}
defineProps
<
Props
>
()
const
questionContent
=
ref
()
const
props
=
defineProps
<
Props
>
()
const
isShowModal
=
defineModel
<
boolean
>
(
'isShowModal'
,
{
required
:
true
})
const
containerWrapperStyle
=
ref
<
{
top
?:
string
;
bottom
?:
string
}
>
({})
const
questionContent
=
ref
(
''
)
const
isShowResponseContentEditModal
=
ref
(
false
)
const
isShowReplaceToolbar
=
ref
(
false
)
const
editResponseContent
=
ref
(
''
)
const
editResponseLoading
=
ref
(
false
)
watchEffect
(()
=>
{
if
(
props
.
isSetBottom
)
{
containerWrapperStyle
.
value
=
{
bottom
:
`
${
props
.
location
}
px`
,
top
:
'unset'
}
}
else
{
containerWrapperStyle
.
value
=
{
top
:
`
${
props
.
location
}
px`
}
}
})
function
handleQuestionSubmitEnter
(
event
:
KeyboardEvent
)
{
if
(
event
.
key
===
'Enter'
&&
!
event
.
shiftKey
)
{
event
.
preventDefault
()
}
}
function
editResponseStatusReset
()
{
isShowModal
.
value
=
false
editResponseContent
.
value
=
''
isShowResponseContentEditModal
.
value
=
true
editResponseLoading
.
value
=
true
isShowReplaceToolbar
.
value
=
false
}
function
handleRetouching
()
{
const
selectContent
=
props
.
editor
?.
selection
.
getContent
()
editResponseStatusReset
()
fetchEventStreamSource
(
'/aiGcRest/articlePolish.json'
,
{
articleContent
:
selectContent
||
''
,
editorRequired
:
questionContent
.
value
||
''
,
},
{
onResponse
:
(
res
)
=>
{
editResponseContent
.
value
+=
res
.
message
},
onend
:
()
=>
{
editResponseLoading
.
value
=
false
isShowReplaceToolbar
.
value
=
true
},
},
)
}
</
script
>
<
template
>
<div
class=
"absolute w-full px-[16px]"
:style=
"
{ top: `${containerTop || 0}px` }">
<div
class=
"rounded-[6px] bg-white py-[6px]"
>
<n-input
v-model:value=
"questionContent"
size=
"large"
type=
"textarea"
placeholder=
"请输入优化文本的指令"
:autosize=
"
{ minRows: 1, maxRows: 3 }"
/>
<Transition
mode=
"out-in"
>
<div
v-if=
"isShowModal"
class=
"absolute top-0 w-full px-[16px]"
:style=
"containerWrapperStyle"
>
<div
class=
"relative rounded-[6px] bg-white py-[10px]"
>
<n-input
v-model:value=
"questionContent"
size=
"large"
type=
"textarea"
placeholder=
"请输入优化文本的指令"
:autosize=
"
{ minRows: 1, maxRows: 3 }"
@keydown="handleQuestionSubmitEnter"
>
<template
#
prefix
>
<i
class=
"iconfont icon-association text-theme-color"
></i>
</
template
>
<
template
#
suffix
>
<i
class=
"iconfont icon-fasong text-[#d0ceff]"
></i>
</
template
>
</n-input>
<ul
class=
"absolute w-[142px] select-none overflow-hidden rounded-[5px] border border-[#9EA3FF] bg-white"
:class=
"isSetBottom ? 'top-[6px] -translate-y-full' : 'bottom-[6px] translate-y-full'"
>
<li
class=
"flex h-[34px] cursor-pointer items-center px-[10px] transition-[background] duration-300 ease-linear hover:bg-[#eeeffe]"
@
click=
"handleRetouching"
>
<i
class=
"iconfont icon-retouching text-[16px] text-[#9ea3ff]"
></i>
<span
class=
"pl-[10px] text-[14px]"
>
润色
</span>
</li>
<li
class=
"flex h-[34px] cursor-pointer items-center px-[10px] transition-[background] duration-300 ease-linear hover:bg-[#eeeffe]"
>
<i
class=
"iconfont icon-expansion text-[16px] text-[#9ea3ff]"
></i>
<span
class=
"pl-[10px] text-[14px]"
>
扩写
</span>
</li>
<li
class=
"flex h-[34px] cursor-pointer items-center px-[10px] transition-[background] duration-300 ease-linear hover:bg-[#eeeffe]"
>
<i
class=
"iconfont icon-suoxie text-[16px] text-[#9ea3ff]"
></i>
<span
class=
"pl-[10px] text-[14px]"
>
缩写
</span>
</li>
<li
class=
"flex h-[34px] cursor-pointer items-center px-[10px] transition-[background] duration-300 ease-linear hover:bg-[#eeeffe]"
>
<i
class=
"iconfont icon-adjust-tone text-[16px] text-[#9ea3ff]"
></i>
<span
class=
"pl-[10px] text-[14px]"
>
调整语气
</span>
</li>
</ul>
</div>
</div>
<div
v-else-if=
"isShowResponseContentEditModal"
class=
"absolute top-1/2 w-full -translate-y-1/2 px-[16px]"
>
<div
class=
"rounded-[5px] border border-[#9EA3FF] bg-white px-[10px] py-[14px]"
>
<div
class=
"text-[14px] leading-[18px]"
>
{{ editResponseContent }}
</div>
<Transition>
<div
v-show=
"editResponseLoading"
class=
"flex select-none items-center justify-between"
>
<span
v-show=
"!editResponseContent"
>
生成中...
</span>
<i
class=
"iconfont icon-zanting cursor-pointer text-[18px]"
></i>
</div>
</Transition>
<Transition>
<div
v-show=
"isShowReplaceToolbar"
class=
"mt-[9px]"
>
<n-button
type=
"primary"
size=
"small"
>
替换
<
template
#
icon
>
<i
class=
"iconfont icon-tihuan"
></i>
</
template
>
</n-button>
<n-button
class=
"!ml-[17px]"
size=
"small"
>
插入
<
template
#
icon
>
<i
class=
"iconfont icon-charu"
></i>
</
template
>
</n-button>
</div>
</Transition>
</div>
</div>
</
div
>
</
Transition
>
</template>
<
style
lang=
"scss"
scoped
>
:deep
(
.n-input
.n-input__state-border
)
{
border-color
:
#9ea3ff
!
important
;
}
.v-enter-active
,
.v-leave-active
{
transition-timing-function
:
ease-in-out
;
transition-duration
:
0
.2s
;
transition-property
:
opacity
;
}
.v-enter-from
,
.v-leave-to
{
opacity
:
0
;
}
</
style
>
src/components/custom-editor/custom-editor.vue
View file @
ad43b680
<
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
EditorToolbar
from
'./editor-toolbar.vue'
import
{
markdownTransformHtml
}
from
'@/utils/markdown-parse'
...
...
@@ -7,6 +7,8 @@ import { downloadFile } from '@/utils/download-file'
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
{
content
:
string
...
...
@@ -31,25 +33,24 @@ defineExpose({
const
editorToolbarRef
=
useTemplateRef
<
InstanceType
<
typeof
EditorToolbar
>>
(
'editorToolbarRef'
)
const
{
t
}
=
useI18n
()
const
editorWrapperRef
=
useTemplateRef
(
'editorWrapperRef'
)
const
{
height
:
editorWrapperRefHeight
}
=
useElementSize
(
editorWrapperRef
)
let
editor
:
any
=
{}
let
controller
:
AbortController
|
null
=
null
const
editorConfig
=
createEditorConfig
()
const
editor
=
shallowRef
<
Editor
|
null
>
(
null
)
const
isShowEditor
=
ref
(
false
)
const
editorWrapperRef
=
ref
<
HTMLDivElement
|
null
>
(
null
)
const
modifyInputContainerStyle
=
ref
({
top
:
'0px'
,
})
const
articleModifyInputContent
=
ref
(
''
)
const
articleContentModifyContainerShow
=
ref
(
false
)
const
articleContentModifyState
=
ref
<
'selection'
|
'loading'
|
'generating'
|
'done'
>
(
'selection'
)
const
articleContentModifyResponseText
=
ref
(
''
)
const
articleContentModifyResponseTextResource
=
ref
(
''
)
const
articleContentModifyContainerClientY
=
ref
(
0
)
const
contentOptimizationEditContainerTop
=
ref
(
300
)
// const contentOptimizationEditContainerTop = ref(300)
const
contentOptimizationEditContainerIsSetBottom
=
ref
(
false
)
const
contentOptimizationEditContainerLocation
=
ref
(
0
)
const
editorContent
=
computed
({
get
()
{
...
...
@@ -63,17 +64,23 @@ const editorContent = computed({
// const editorTitleText = ref('')
watch
(
articleContentModifyResponseTextResource
,
(
newVal
)
=>
{
if
(
newVal
)
if
(
newVal
&&
editor
.
value
)
articleContentModifyResponseText
.
value
=
markdownTransformHtml
(
editor
.
dom
?
editor
.
dom
.
decode
(
newVal
)
:
newVal
,
editor
.
value
.
dom
?
editor
.
value
.
dom
.
decode
(
newVal
)
:
newVal
,
)
as
string
})
watch
(
()
=>
props
.
content
,
(
val
:
string
,
prevVal
:
string
)
=>
{
if
(
editor
&&
editor
.
getContent
&&
typeof
val
===
'string'
&&
val
!==
prevVal
&&
val
!==
editor
.
getContent
())
editor
.
setContent
(
val
)
if
(
editor
.
value
&&
editor
.
value
.
getContent
&&
typeof
val
===
'string'
&&
val
!==
prevVal
&&
val
!==
editor
.
value
.
getContent
()
)
editor
.
value
.
setContent
(
val
)
},
)
...
...
@@ -82,7 +89,7 @@ onMounted(() => {
...
editorConfig
,
init_instance_callback
:
(
editorInstance
)
=>
{
isShowEditor
.
value
=
true
editor
=
editorInstance
editor
.
value
=
editorInstance
nextTick
(()
=>
{
props
.
content
&&
editorInstance
.
setContent
(
props
.
content
)
...
...
@@ -113,8 +120,6 @@ onMounted(() => {
editorInstance
.
on
(
'mousedown'
,
(
e
:
MouseEvent
)
=>
{
if
(
e
.
button
!==
0
)
return
articleContentModifyContainerClientY
.
value
=
e
.
clientY
if
(
articleContentModifyContainerShow
.
value
)
{
if
(
controller
)
{
controller
.
abort
()
...
...
@@ -125,7 +130,6 @@ onMounted(() => {
articleContentModifyResponseText
.
value
=
''
articleContentModifyResponseTextResource
.
value
=
''
articleContentModifyState
.
value
=
'selection'
articleModifyInputContent
.
value
=
''
}
editorInstance
.
selection
.
collapse
()
...
...
@@ -146,25 +150,24 @@ onMounted(() => {
if
(
lastRect
)
{
/* 其中 46 是当前编辑器文档HTMl距离 外部挂载容器之间产生的高度 */
contentOptimizationEditContainerTop
.
value
=
lastRect
.
bottom
+
46
+
10
// contentOptimizationEditContainerTop.value = locationEl.offsetTop + locationEl.offsetHeight
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
}
}
const
top
=
(
e
.
clientY
>
articleContentModifyContainerClientY
.
value
?
e
.
clientY
:
articleContentModifyContainerClientY
.
value
)
+
130
const
bodyHeight
=
document
.
body
.
offsetHeight
modifyInputContainerStyle
.
value
.
top
=
`
${
top
>
bodyHeight
-
400
?
bodyHeight
-
400
:
top
}
px`
articleContentModifyContainerShow
.
value
=
true
}
})
editorInstance
.
on
(
'keydown'
,
()
=>
{
if
(
articleContentModifyContainerShow
.
value
)
articleContentModifyContainerShow
.
value
=
false
//
if (articleContentModifyContainerShow.value) articleContentModifyContainerShow.value = false
})
},
})
...
...
@@ -185,7 +188,7 @@ onMounted(() => {
function
getContent
()
{
if
(
!
isShowEditor
.
value
)
return
''
const
content
=
editor
.
getContent
()
const
content
=
editor
.
value
?.
getContent
()
||
''
const
parser
=
window
.
tinymce
.
html
.
DomParser
({
validate
:
false
})
parser
.
addNodeFilter
(
'mf-confirmation-box'
,
(
nodes
)
=>
{
...
...
@@ -229,7 +232,12 @@ function onDownloadFile() {
</div>
</div>
<ContentOptimizationEdit
:container-top=
"contentOptimizationEditContainerTop"
/>
<ContentOptimizationEdit
v-model:is-show-modal=
"articleContentModifyContainerShow"
:editor=
"editor"
:is-set-bottom=
"contentOptimizationEditContainerIsSetBottom"
:location=
"contentOptimizationEditContainerLocation"
/>
</div>
<div
v-show=
"!isShowEditor"
class=
"flex h-full w-full items-center justify-center"
>
<n-spin
size=
"large"
/>
...
...
src/components/custom-editor/utils/fetch-event-stream-source.ts
0 → 100644
View file @
ad43b680
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'
interface
ResponseData
{
message
:
string
reasoningContent
:
string
function
:
{
name
:
string
}
}
interface
Options
{
onResponse
?:
(
data
:
ResponseData
)
=>
void
onend
?:
()
=>
void
onclose
?:
()
=>
void
onerror
?:
(
err
:
Error
)
=>
void
}
export
default
function
fetchEventStreamSource
(
url
:
string
,
payload
:
object
=
{},
options
:
Options
=
{
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
,
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/components/editor-drawer/editor-drawer.vue
View file @
ad43b680
...
...
@@ -4,7 +4,7 @@ import CustomEditor from '@/components/custom-editor/custom-editor.vue'
const
contentEdit
=
defineModel
<
string
>
(
'contentEdit'
,
{
required
:
true
})
const
isShowEditorDrawerDraft
=
true
const
contentEditDraft
=
`<h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h
1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h4>济南的冬天,是一幅独特的画卷。它不同于北方的严寒,也不同于南方的温润。这里的冬天,有着独特的魅力和风情。济南的冬日,天空湛蓝,阳光明媚,尽管寒风凛冽,但总能带给人一份宁静和温馨。济南的泉水在冬天依然潺潺流淌,为这座城市增添了一份生机和活力。济南的冬日,不仅是季节的更迭,更是一种生活的体验,一种对大自然的敬畏和感慨。</h4
>`
const
contentEditDraft
=
`<h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h
4>济南的冬天,是一幅独特的画卷。它不同于北方的严寒,也不同于南方的温润。这里的冬天,有着独特的魅力和风情。济南的冬日,天空湛蓝,阳光明媚,尽管寒风凛冽,但总能带给人一份宁静和温馨。济南的泉水在冬天依然潺潺流淌,为这座城市增添了一份生机和活力。济南的冬日,不仅是季节的更迭,更是一种生活的体验,一种对大自然的敬畏和感慨。</h4><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h1>标题</h1><h4>济南的冬天,是一幅独特的画卷。它不同于北方的严寒,也不同于南方的温润。这里的冬天,有着独特的魅力和风情。济南的冬日,天空湛蓝,阳光明媚,尽管寒风凛冽,但总能带给人一份宁静和温馨。济南的泉水在冬天依然潺潺流淌,为这座城市增添了一份生机和活力。济南的冬日,不仅是季节的更迭,更是一种生活的体验,一种对大自然的敬畏和感慨。</h4><h1>标题</h1><h1>标题</h1
>`
const
isShowEditorDrawer
=
defineModel
<
boolean
>
(
'isShowEditorDrawer'
,
{
required
:
true
})
...
...
src/config/base-url.ts
View file @
ad43b680
export
const
BASE_URLS
:
Record
<
'DEV'
|
'PROD'
,
string
>
=
{
DEV
:
'https://poc-sit.gsstcloud.com'
,
// DEV: 'https://poc-sit.gsstcloud.com',
DEV
:
'http://localhost:8848'
,
PROD
:
'https://model-link.gsstcloud.com'
,
}
...
...
vite.config.ts
View file @
ad43b680
...
...
@@ -27,6 +27,15 @@ export default defineConfig(({ command, mode }) => {
server
:
{
host
:
true
,
port
:
envConf
.
VITE_PORT
,
proxy
:
{
'/api/rest'
:
{
target
:
'http://192.168.13.93:5000'
,
changeOrigin
:
true
,
rewrite
:
(
path
)
=>
{
return
path
},
},
},
},
css
:
{
preprocessorOptions
:
{
...
...
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