diff --git a/GinSkeleton/app/http/controller/web/style_controller.go b/GinSkeleton/app/http/controller/web/style_controller.go new file mode 100644 index 0000000..4d19c66 --- /dev/null +++ b/GinSkeleton/app/http/controller/web/style_controller.go @@ -0,0 +1,213 @@ +package web + +import ( + "github.com/gin-gonic/gin" + "goskeleton/app/global/consts" + "goskeleton/app/global/variable" + "goskeleton/app/utils/response" + "os" + "encoding/json" + "strings" +) + +type Editor struct { +} + +func (e *Editor) UserStyleSave(context *gin.Context) { + // 获取用户输入 + userId, _ := context.Get(consts.ValidatorPrefix + "user_name") + styleName, _ := context.Get(consts.ValidatorPrefix + "style_name") + elementName, _ := context.Get(consts.ValidatorPrefix + "element_name") + styleClasses, _ := context.Get(consts.ValidatorPrefix + "style_classes") + styleContent, _ := context.Get(consts.ValidatorPrefix + "style_content") + + // 转换 style classes + var styleClassesStr []string + for _, v := range styleClasses.([]interface{}) { + styleClassesStr = append(styleClassesStr, v.(string)) + } + + // 准备新的样式数据 + rowData := map[string]interface{}{ + "style_name": styleName.(string), + "element_name": elementName.(string), + "style_classes": styleClassesStr, + "style_content": styleContent.(string), + } + + userStyleDefinitionPath := variable.BasePath + variable.ConfigYml.GetString("Style.UserStyleSavePath") + "/" + userId.(string) + ".json" + + var existingStyles []map[string]interface{} + + // 检查文件是否存在 + if _, err := os.Stat(userStyleDefinitionPath); os.IsNotExist(err) { + // 文件不存在,创建新的数组 + existingStyles = []map[string]interface{}{rowData} + } else { + // 文件存在,读取现有内容 + existingData, err := os.ReadFile(userStyleDefinitionPath) + if err != nil { + response.ErrorSystem(context, "无法读取样式文件", "") + return + } + + // 解析现有的 JSON 数据 + if err := json.Unmarshal(existingData, &existingStyles); err != nil { + response.ErrorSystem(context, "无法解析样式文件", "") + return + } + + // 查找是否存在相同的 style_name + found := false + for i, style := range existingStyles { + if style["style_name"] == styleName.(string) { + // 找到相同的 style_name,更新内容 + existingStyles[i] = rowData + found = true + break + } + } + + // 如果没有找到相同的 style_name,则追加新的样式 + if !found { + existingStyles = append(existingStyles, rowData) + } + } + + // 将更新后的数据转换为 JSON,并使用缩进格式 + data, err := json.MarshalIndent(existingStyles, "", " ") + if err != nil { + response.ErrorSystem(context, "无法序列化样式数据", "") + return + } + + // 写入文件(覆盖原文件) + if err := os.WriteFile(userStyleDefinitionPath, data, 0644); err != nil { + response.ErrorSystem(context, "无法写入样式文件", "") + return + } + + response.Success(context, "UserStyleSave", "") +} + +func (e *Editor) UserStyleGet(context *gin.Context) { + userId, _ := context.Get(consts.ValidatorPrefix + "user_name") + userStyleDefinitionPath := variable.BasePath + variable.ConfigYml.GetString("Style.UserStyleSavePath") + "/" + userId.(string) + ".json" + + // 检查文件是否存在 + if _, err := os.Stat(userStyleDefinitionPath); os.IsNotExist(err) { + // 如果文件不存在,返回空数组而不是错误 + response.Success(context, "UserStyleGet", []map[string]interface{}{}) + return + } + + // 读取文件内容 + existingData, err := os.ReadFile(userStyleDefinitionPath) + if err != nil { + response.ErrorSystem(context, "无法读取样式文件", "") + return + } + + // 使用与 UserStyleSave 相同的数据类型 + var existingStyles []map[string]interface{} + if err := json.Unmarshal(existingData, &existingStyles); err != nil { + response.ErrorSystem(context, "无法解析样式文件", "") + return + } + + response.Success(context, "UserStyleGet", existingStyles) +} + +func (e *Editor) AiFormatConfigSave(context *gin.Context) { + // 获取用户输入 + id, _ := context.Get(consts.ValidatorPrefix + "user_name") + titleStyle, _ := context.Get(consts.ValidatorPrefix + "title_style") + headingStyle, _ := context.Get(consts.ValidatorPrefix + "heading_style") + listStyle, _ := context.Get(consts.ValidatorPrefix + "list_style") + bodyStyle, _ := context.Get(consts.ValidatorPrefix + "body_style") + blockquoteStyle, _ := context.Get(consts.ValidatorPrefix + "blockquote_style") + codeblockStyle, _ := context.Get(consts.ValidatorPrefix + "codeblock_style") + + // 处理 headingStyle + var headingStyleArray [][]string + if headingStr, ok := headingStyle.(string); ok { + // 先按逗号分割 + headings := strings.Split(headingStr, ",") + // 每两个元素组成一对 + for i := 0; i < len(headings); i += 2 { + if i+1 < len(headings) { + pair := []string{headings[i], headings[i+1]} + headingStyleArray = append(headingStyleArray, pair) + } + } + } + + // 处理 codeblockStyle + var codeblockStyleArray []string + if codeblockStr, ok := codeblockStyle.(string); ok { + codeblockStyleArray = strings.Split(codeblockStr, ",") + } + + // 准备配置数据 + rowData := map[string]interface{}{ + "title_style": titleStyle.(string), + "heading_style": headingStyleArray, // 使用处理后的二维数组 + "list_style": listStyle.(string), + "body_style": bodyStyle.(string), + "blockquote_style": blockquoteStyle.(string), + "codeblock_style": codeblockStyleArray, // 使用处理后的数组 + } + + aiFormatConfigPath := variable.BasePath + variable.ConfigYml.GetString("Style.UserFormatConfigSavePath") + "/" + id.(string) + ".json" + + // 将数据转换为格式化的 JSON + data, err := json.MarshalIndent(rowData, "", " ") + if err != nil { + response.ErrorSystem(context, "无法序列化AI格式配置数据", "") + return + } + + // 写入文件(覆盖原文件) + if err := os.WriteFile(aiFormatConfigPath, data, 0644); err != nil { + response.ErrorSystem(context, "无法写入AI格式配置文件", "") + return + } + + response.Success(context, "AiFormatConfigSave", "") +} + +func (e *Editor) AiFormatConfigGet(context *gin.Context) { + id, _ := context.Get(consts.ValidatorPrefix + "user_name") + aiFormatConfigPath := variable.BasePath + variable.ConfigYml.GetString("Style.UserFormatConfigSavePath") + "/" + id.(string) + ".json" + + // 检查文件是否存在 + if _, err := os.Stat(aiFormatConfigPath); os.IsNotExist(err) { + // 返回默认空配置而不是错误 + defaultConfig := map[string]interface{}{ + "title_style": "", + "heading_style": []string{}, + "list_style": "", + "body_style": "", + "blockquote_style": "", + "codeblock_style": "", + } + response.Success(context, "AiFormatConfigGet", defaultConfig) + return + } + + // 读取文件内容 + existingData, err := os.ReadFile(aiFormatConfigPath) + if err != nil { + response.ErrorSystem(context, "无法读取AI格式配置文件", "") + return + } + + // 解析 JSON 数据 + var existingStyles map[string]interface{} + if err := json.Unmarshal(existingData, &existingStyles); err != nil { + response.ErrorSystem(context, "无法解析AI格式配置文件", "") + return + } + + response.Success(context, "AiFormatConfigGet", existingStyles) +} \ No newline at end of file diff --git a/GinSkeleton/app/http/middleware/cors/cors.go b/GinSkeleton/app/http/middleware/cors/cors.go index 305ed05..cc206f4 100644 --- a/GinSkeleton/app/http/middleware/cors/cors.go +++ b/GinSkeleton/app/http/middleware/cors/cors.go @@ -18,6 +18,7 @@ func Next() gin.HandlerFunc { // 放行所有OPTIONS方法 if method == "OPTIONS" { c.AbortWithStatus(http.StatusAccepted) + return } c.Next() } diff --git a/GinSkeleton/app/http/validator/common/register_validator/web_register_validator.go b/GinSkeleton/app/http/validator/common/register_validator/web_register_validator.go index 110238d..2b2cde8 100644 --- a/GinSkeleton/app/http/validator/common/register_validator/web_register_validator.go +++ b/GinSkeleton/app/http/validator/common/register_validator/web_register_validator.go @@ -10,6 +10,7 @@ import ( "goskeleton/app/http/validator/web/ai_recognition" "goskeleton/app/http/validator/web/file" "goskeleton/app/http/validator/web/users" + "goskeleton/app/http/validator/web/editor" ) // 各个业务模块验证器必须进行注册(初始化),程序启动时会自动加载到容器 @@ -91,4 +92,19 @@ func WebRegisterValidator() { key = consts.ValidatorPrefix + "FolderGet" containers.Set(key, file.FolderGet{}) + // 用户配置相关 + // 用户样式保存 + key = consts.ValidatorPrefix + "UserStyleSave" + containers.Set(key, editor.UserStyleSave{}) + // 用户样式获取 + key = consts.ValidatorPrefix + "UserStyleGet" + containers.Set(key, editor.UserStyleGet{}) + // AI格式配置保存 + key = consts.ValidatorPrefix + "AiFormatConfigSave" + containers.Set(key, editor.AiFormatConfigSave{}) + // AI格式配置获取 + key = consts.ValidatorPrefix + "AiFormatConfigGet" + containers.Set(key, editor.AiFormatConfigGet{}) + + } diff --git a/GinSkeleton/app/http/validator/web/editor/style.go b/GinSkeleton/app/http/validator/web/editor/style.go new file mode 100644 index 0000000..6f95d46 --- /dev/null +++ b/GinSkeleton/app/http/validator/web/editor/style.go @@ -0,0 +1,84 @@ +package editor + +import ( + "github.com/gin-gonic/gin" + "goskeleton/app/global/consts" + "goskeleton/app/http/controller/web" + "goskeleton/app/http/validator/core/data_transfer" + "goskeleton/app/utils/response" +) + +type UserStyleSave struct{ + UserName string `form:"user_name" json:"user_name" ` + StyleName string `form:"style_name" json:"style_name" ` + ElementName string `form:"element_name" json:"element_name" ` + StyleClasses []string `form:"style_classes" json:"style_classes"` + StyleContent string `form:"style_content" json:"style_content" ` +} +func (u UserStyleSave) CheckParams(context *gin.Context) { + if err := context.ShouldBind(&u); err != nil { + response.ValidatorError(context, err) + return + } + extraAddBindDataContext := data_transfer.DataAddContext(u, consts.ValidatorPrefix, context) + if extraAddBindDataContext == nil { + response.ErrorSystem(context, "UserStyleSave表单验证器json化失败", "") + } else { + (&web.Editor{}).UserStyleSave(extraAddBindDataContext) + } +} +type UserStyleGet struct{ + UserName string `form:"user_name" json:"user_name" binding:"required,min=1"` +} +func (u UserStyleGet) CheckParams(context *gin.Context) { + if err := context.ShouldBind(&u); err != nil { + response.ValidatorError(context, err) + return + } + extraAddBindDataContext := data_transfer.DataAddContext(u, consts.ValidatorPrefix, context) + if extraAddBindDataContext == nil { + response.ErrorSystem(context, "UserStyleGet表单验证器json化失败", "") + } else { + (&web.Editor{}).UserStyleGet(extraAddBindDataContext) + } +} + +type AiFormatConfigSave struct{ + UserName string `form:"user_name" json:"user_name" binding:"required,min=1"` + TitleStyle string `form:"title_style" json:"title_style" binding:"required,min=1"` + HeadingStyle string `form:"heading_style" json:"heading_style" binding:"required,min=1"` + ListStyle string `form:"list_style" json:"list_style" binding:"required,min=1"` + BodyStyle string `form:"body_style" json:"body_style" binding:"required,min=1"` + BlockquoteStyle string `form:"blockquote_style" json:"blockquote_style" binding:"required,min=1"` + CodeblockStyle string `form:"codeblock_style" json:"codeblock_style" binding:"required,min=1"` +} + +func (u AiFormatConfigSave) CheckParams(context *gin.Context) { + if err := context.ShouldBind(&u); err != nil { + response.ValidatorError(context, err) + return + } + extraAddBindDataContext := data_transfer.DataAddContext(u, consts.ValidatorPrefix, context) + if extraAddBindDataContext == nil { + response.ErrorSystem(context, "AiFormatConfigSave表单验证器json化失败", "") + } else { + (&web.Editor{}).AiFormatConfigSave(extraAddBindDataContext) + } +} + +type AiFormatConfigGet struct{ + UserName string `form:"user_name" json:"user_name" binding:"required,min=1"` +} + +func (u AiFormatConfigGet) CheckParams(context *gin.Context) { + if err := context.ShouldBind(&u); err != nil { + response.ValidatorError(context, err) + return + } + extraAddBindDataContext := data_transfer.DataAddContext(u, consts.ValidatorPrefix, context) + if extraAddBindDataContext == nil { + response.ErrorSystem(context, "AiFormatConfigGet表单验证器json化失败", "") + } else { + (&web.Editor{}).AiFormatConfigGet(extraAddBindDataContext) + } +} \ No newline at end of file diff --git a/GinSkeleton/config/config.yml b/GinSkeleton/config/config.yml index ac86283..ceea2d6 100644 --- a/GinSkeleton/config/config.yml +++ b/GinSkeleton/config/config.yml @@ -155,3 +155,7 @@ BaiduCE: QianFanSecretKey: "cb812e1b6e56420ea858d160e1351869" StyleGeneratePromptPath: "/storage/app/prompt/style_generate.prompt" # 生成样式的提示词保存路径 LayoutGeneratePromptPath: "/storage/app/prompt/layout_generate.prompt" # 生成布局的提示词保存路径 + +Style: + UserFormatConfigSavePath: "/storage/user_format_config" + UserStyleSavePath: "/storage/user_styles" \ No newline at end of file diff --git a/GinSkeleton/routers/web.go b/GinSkeleton/routers/web.go index 6f7b880..4bf6785 100644 --- a/GinSkeleton/routers/web.go +++ b/GinSkeleton/routers/web.go @@ -87,6 +87,19 @@ func InitWebRouter_Co() *gin.Engine { aiLayout.POST("layout_generate", validatorFactory.Create(consts.ValidatorPrefix+"LayoutGenerate")) } + // 用户配置相关 + userConfig := backend.Group("user_config/") + { + // 用户样式保存 + userConfig.POST("user_style_save", validatorFactory.Create(consts.ValidatorPrefix+"UserStyleSave")) + // 用户样式获取 + userConfig.POST("user_style_get", validatorFactory.Create(consts.ValidatorPrefix+"UserStyleGet")) + // AI格式配置保存 + userConfig.POST("ai_format_config_save", validatorFactory.Create(consts.ValidatorPrefix+"AiFormatConfigSave")) + // AI格式配置获取 + userConfig.POST("ai_format_config_get", validatorFactory.Create(consts.ValidatorPrefix+"AiFormatConfigGet")) + } + // 【需要token】中间件验证的路由 backend.Use(authorization.CheckTokenAuth()) { diff --git a/GinSkeleton/storage/user_format_config/123.json b/GinSkeleton/storage/user_format_config/123.json new file mode 100644 index 0000000..de8c24a --- /dev/null +++ b/GinSkeleton/storage/user_format_config/123.json @@ -0,0 +1,24 @@ +{ + "blockquote_style": "side-quote", + "body_style": "normal-text", + "codeblock_style": [ + "fancy-code", + "fancy-code-dark" + ], + "heading_style": [ + [ + "h3", + "heading1" + ], + [ + "h4", + "heading2" + ], + [ + "h5", + "heading3" + ] + ], + "list_style": "disc", + "title_style": "title1" +} \ No newline at end of file diff --git a/GinSkeleton/storage/user_styles/123.json b/GinSkeleton/storage/user_styles/123.json new file mode 100644 index 0000000..87631e2 --- /dev/null +++ b/GinSkeleton/storage/user_styles/123.json @@ -0,0 +1,26 @@ +[ + { + "element_name": "p", + "style_classes": [ + "a" + ], + "style_content": ".ck-content p.a {\r\n color: green;\r\n}", + "style_name": "a" + }, + { + "element_name": "p", + "style_classes": [ + "aa" + ], + "style_content": ".ck-content p.aa {\r\n color: green;\r\n}", + "style_name": "aa" + }, + { + "element_name": "p", + "style_classes": [ + "test" + ], + "style_content": ".ck-content p.test {\r\n font-family: fantasy;\r\n color: orange;\r\n font-style: italic;\r\n text-shadow: #FC0 1px 0 10px;\r\n}", + "style_name": "test" + } +] \ No newline at end of file diff --git a/coeditor_frontend/src/components/plugins.js b/coeditor_frontend/src/components/plugins.js index e02a565..5f69c56 100644 --- a/coeditor_frontend/src/components/plugins.js +++ b/coeditor_frontend/src/components/plugins.js @@ -74,7 +74,16 @@ import { createDropdown, Collection, addListToDropdown, + View, + // LabeledFieldView, + ContextualBalloon, + // createLabeledInputText + // InputTextView + Model, + ListView, + ListItemView } from 'ckeditor5'; +// import View from '@ckeditor/ckeditor5-ui/src/view'; import translations from 'ckeditor5/translations/zh-cn.js'; import { asBlob } from 'html-docx-js-typescript' import { saveAs } from 'file-saver'; @@ -85,7 +94,8 @@ import { saveData, markdown2html, html2markdown, - getUserAILayoutConfig + getUserAILayoutConfig, + saveUserAILayoutConfig } from './utils'; import mitt from 'mitt'; // 导出为docx插件 @@ -130,24 +140,6 @@ class Export2Word extends Plugin { return button; }); - // 增加菜单栏? 不显示按钮 - // editor.ui.extendMenuBar({ - // menu: { - // menuId: 'export', - // label: '导出', - // groups: [ - // { - // groupId: 'export', - // items: [ - // 'ExportToWord' - // ] - // } - // ] - // }, - // position: 'after:help' - // } - // ); - } } @@ -384,6 +376,877 @@ class SaveButton extends Plugin { }); } } + +class SettingFormView extends View { + constructor(locale) { + super(locale); + + // 创建所有配置项的集合 + this._createDropdowns(); + this._createButtons(); + + this.setTemplate({ + tag: 'form', + attributes: { + class: ['ck', 'ck-setting-form'], + tabindex: '-1' + }, + children: [ + { + tag: 'div', + attributes: { + class: ['ck', 'ck-setting-form-content'] + }, + children: [ + this._createSection('标题样式', [this.titleStyleDropdown]), + ...this.headingStyleDropdowns.map((heading, index) => + this._createSection(`${index + 1}级标题`, [heading.size, heading.style]) + ), + this._createSection('列表样式', [this.listStyleDropdown]), + this._createSection('正文样式', [this.bodyStyleDropdown]), + this._createSection('引用样式', [this.blockquoteStyleDropdown]), + this._createSection('代码块样式', [this.codeBlockStyleDropdown]) + ] + }, + { + tag: 'div', + attributes: { + class: ['ck', 'ck-setting-form-actions'] + }, + children: [ + this.saveButtonView, + this.cancelButtonView + ] + } + ] + }); + } + + _createDropdowns() { + this.titleStyleDropdown = this._createDropdownWithStyle('文章标题样式', [ + { text: '样式1', value: 'title1', class: 'ck-title1' }, + { text: '样式2', value: 'title2', class: 'ck-title2' }, + { text: '样式3', value: 'title3', class: 'ck-title3' } + ]); + + this.headingStyleDropdowns = [ + { + size: this._createDropdownWithStyle('一级标题大小', [ + { text: 'H3', value: 'h3',class: 'ck-h3' }, + { text: 'H4', value: 'h4',class: 'ck-h4' }, + { text: 'H5', value: 'h5',class: 'ck-h5' } + ]), + style: this._createDropdown('一级标题样式', [ + { text: '(一) (二)', value: 'heading1'}, + { text: '一、 二、', value: 'heading2'}, + { text: '1. 2.', value: 'heading3'}, + { text: '1) 2)', value: 'heading4'}, + { text: '第一章 第二章', value: 'heading5'}, + { text: '第一小节 第二小节', value: 'heading6'}, + { text: 'I. II.', value: 'heading7'}, + { text: 'A. B.', value: 'heading8'}, + { text: 'a. b.', value: 'heading9'}, + { text: 'i. ii.', value: 'heading10'}, + ]) + }, + { + size: this._createDropdownWithStyle('二级标题大小', [ + { text: 'H3', value: 'h3',class: 'ck-h3' }, + { text: 'H4', value: 'h4',class: 'ck-h4' }, + { text: 'H5', value: 'h5',class: 'ck-h5' } + ]), + style: this._createDropdown('二级标题样式', [ + { text: '(一) (二)', value: 'heading1'}, + { text: '一、 二、', value: 'heading2'}, + { text: '1. 2.', value: 'heading3'}, + { text: '1) 2)', value: 'heading4'}, + { text: '第一章 第二章', value: 'heading5'}, + { text: '第一小节 第二小节', value: 'heading6'}, + { text: 'I. II.', value: 'heading7'}, + { text: 'A. B.', value: 'heading8'}, + { text: 'a. b.', value: 'heading9'}, + { text: 'i. ii.', value: 'heading10'}, + ]) + }, + { + size: this._createDropdownWithStyle('三级标题大小', [ + { text: 'H3', value: 'h3',class: 'ck-h3' }, + { text: 'H4', value: 'h4',class: 'ck-h4' }, + { text: 'H5', value: 'h5',class: 'ck-h5' } + ]), + style: this._createDropdown('三级标题样式', [ + { text: '(一) (二)', value: 'heading1'}, + { text: '一、 二、', value: 'heading2'}, + { text: '1. 2.', value: 'heading3'}, + { text: '1) 2)', value: 'heading4'}, + { text: '第一章 第二章', value: 'heading5'}, + { text: '第一小节 第二小节', value: 'heading6'}, + { text: 'I. II.', value: 'heading7'}, + { text: 'A. B.', value: 'heading8'}, + { text: 'a. b.', value: 'heading9'}, + { text: 'i. ii.', value: 'heading10'}, + ]) + } + ]; + + this.listStyleDropdown = this._createDropdownWithList('列表样式', [ + { text: '实心圆点', value: 'disc',class: 'ck-list-disc' }, + { text: '空心圆圈', value: 'circle', class: 'ck-list-circle' }, + { text: '方块', value: 'square', class: 'ck-list-square' }, + ]); + + this.bodyStyleDropdown = this._createDropdownWithStyle('正文样式', [ + { text: '默认', value: 'normal-text', class: 'ck-normal-text' }, + { text: '四号宋体', value: 'songti-text', class: 'ck-songti-text' }, + { text: '五号楷体', value: 'kaiti-text', class: 'ck-kaiti-text' }, + ]); + + this.blockquoteStyleDropdown = this._createDropdownWithSideQuote('引用样式', [ + { text: '侧引', value: 'side-quote',class: 'ck-side-quote' }, + ]); + + this.codeBlockStyleDropdown = this._createDropdownWithCodeBlock('代码块样式', [ + { text: '经典暗色', value: 'fancy-code fancy-code-dark',class: 'ck-fancy-code ck-fancy-code-dark' }, + { text: '明亮主题', value: 'fancy-code fancy-code-bright',class: 'ck-fancy-code ck-fancy-code-bright' }, + ]); + } + + _createDropdown(label, items) { + const dropdown = createDropdown(this.locale); + const listView = new ListView(this.locale); + + dropdown.buttonView.set({ + label, + withText: true, + class: 'ck-dropdown-button' + }); + + // 创建列表项集合 + const itemsList = new Collection(); + for (const item of items) { + const listItem = new ListItemView(this.locale); + listItem.children.add(new ButtonView(this.locale)); + listItem.children.first.set({ + label: item.text, + withText: true + }); + + // 绑定点击事件 + listItem.children.first.on('execute', () => { + dropdown.buttonView.label = item.text; + dropdown.value = item.value; + dropdown.isOpen = false; + }); + + itemsList.add(listItem); + } + + listView.items.bindTo(itemsList).using(item => item); + dropdown.panelView.children.add(listView); + + return dropdown; + } + + _createDropdownWithStyle(label, items) { + const dropdown = createDropdown(this.locale); + const listView = new ListView(this.locale); + + dropdown.buttonView.set({ + label, + withText: true, + class: ['ck', 'ck-dropdown-button'] + }); + + const itemsList = new Collection(); + for (const item of items) { + const listItem = new ListItemView(this.locale); + + // 创建按钮视图 + const buttonView = new ButtonView(this.locale); + + // 创建预览模型 + const previewModel = new Model({ + label: item.text, + withText: true, + class: ['ck', 'ck-style-preview', 'ck-style-preview--rich'] + }); + + // 设置按钮视图的模板 + buttonView.extendTemplate({ + attributes: { + class: ['ck', 'ck-button', 'ck-style-preview'] + } + }); + + // 创建预览内容视图 + const previewContent = new View(this.locale); + previewContent.setTemplate({ + tag: 'div', + attributes: { + class: [ + 'ck', + 'ck-style-preview-content', + item.class + ] + }, + children: [ + { + tag: 'div', + attributes: { + class: ['ck-style-preview-sample'] + }, + children: [ + { + text: item.sample || item.text + } + ] + } + ] + }); + + buttonView.children.add(previewContent); + buttonView.bind('label').to(previewModel, 'label'); + + // 绑定点击事件 + buttonView.on('execute', () => { + dropdown.buttonView.label = item.text; + dropdown.value = item.value; + dropdown.isOpen = false; + + // 触发change事件 + this.fire('change', { + value: item.value, + style: item.class + }); + }); + + listItem.children.add(buttonView); + itemsList.add(listItem); + } + + listView.items.bindTo(itemsList).using(item => item); + dropdown.panelView.children.add(listView); + + return dropdown; + } + + _createDropdownWithList(label, items) { + const dropdown = createDropdown(this.locale); + const listView = new ListView(this.locale); + + dropdown.buttonView.set({ + label, + withText: true, + class: ['ck', 'ck-dropdown-button'], + }); + + const itemsList = new Collection(); + for (const item of items) { + const listItem = new ListItemView(this.locale); + + // 创建按钮视图 + const buttonView = new ButtonView(this.locale); + + // 创建预览模型 + const previewModel = new Model({ + label: item.text, + withText: true, + class: ['ck', 'ck-style-preview', 'ck-style-preview--rich'], + + }); + + // 设置按钮视图的模板 + buttonView.extendTemplate({ + attributes: { + class: ['ck', 'ck-button', 'ck-style-preview'] + } + }); + + // 创建预览内容视图 + const previewContent = new View(this.locale); + previewContent.setTemplate({ + tag: 'ul', + attributes: { + class: [ + 'ck', + 'ck-style-preview-content', + // item.class + + ], + // item.value disc circle square + style: { + listStyleType: item.value + } + }, + children: [ + { + tag: 'li', + attributes: { + class: ['ck-style-preview-sample'] + }, + children: [ + { + text: item.sample || item.text + } + ] + } + ] + }); + + buttonView.children.add(previewContent); + buttonView.bind('label').to(previewModel, 'label'); + + // 绑定点击事件 + buttonView.on('execute', () => { + dropdown.buttonView.label = item.text; + dropdown.value = item.value; + dropdown.isOpen = false; + + // 触发change事件 + this.fire('change', { + value: item.value, + style: item.class + }); + }); + + listItem.children.add(buttonView); + itemsList.add(listItem); + } + + listView.items.bindTo(itemsList).using(item => item); + dropdown.panelView.children.add(listView); + + return dropdown; + } + + _createDropdownWithSideQuote(label, items) { + const dropdown = createDropdown(this.locale); + const listView = new ListView(this.locale); + + dropdown.buttonView.set({ + label, + withText: true, + class: ['ck', 'ck-dropdown-button'] + }); + + const itemsList = new Collection(); + for (const item of items) { + const listItem = new ListItemView(this.locale); + + // 创建按钮视图 + const buttonView = new ButtonView(this.locale); + + // 创建预览模型 + const previewModel = new Model({ + label: item.text, + withText: true, + class: ['ck', 'ck-style-preview', 'ck-style-preview--rich'] + }); + + // 设置按钮视图的模板 + buttonView.extendTemplate({ + attributes: { + class: ['ck', 'ck-button', 'ck-style-preview'] + } + }); + + // 创建预览内容视图 + const previewContent = new View(this.locale); + previewContent.setTemplate({ + tag: 'div', + attributes: { + class: [ + 'ck', + 'ck-style-preview-content', + item.class + ] + }, + children: [ + { + tag: 'blockquote', + attributes: { + class: ['ck-style-preview-sample'] + }, + children: [ + { + tag: 'p', + // text: item.sample || item.text + // text: '引用内容' + children: [ + { + text: item.sample || item.text + } + ] + } + ] + } + ] + }); + + buttonView.children.add(previewContent); + buttonView.bind('label').to(previewModel, 'label'); + + // 绑定点击事件 + buttonView.on('execute', () => { + dropdown.buttonView.label = item.text; + dropdown.value = item.value; + dropdown.isOpen = false; + + // 触发change事件 + this.fire('change', { + value: item.value, + style: item.class + }); + }); + + listItem.children.add(buttonView); + itemsList.add(listItem); + } + + listView.items.bindTo(itemsList).using(item => item); + dropdown.panelView.children.add(listView); + + return dropdown; + } + + _createDropdownWithCodeBlock(label, items) { + const dropdown = createDropdown(this.locale); + const listView = new ListView(this.locale); + + dropdown.buttonView.set({ + label, + withText: true, + class: ['ck', 'ck-dropdown-button'] + }); + + const itemsList = new Collection(); + for (const item of items) { + const listItem = new ListItemView(this.locale); + + // 创建按钮视图 + const buttonView = new ButtonView(this.locale); + + // 创建预览模型 + const previewModel = new Model({ + label: item.text, + withText: true, + class: ['ck', 'ck-style-preview', 'ck-style-preview--rich'] + }); + + // 设置按钮视图的模板 + buttonView.extendTemplate({ + attributes: { + class: ['ck', 'ck-button', 'ck-style-preview'] + } + }); + + // 创建预览内容视图 + const previewContent = new View(this.locale); + previewContent.setTemplate({ + tag: 'pre', + attributes: { + class: [ + 'ck', + 'ck-style-preview-content', + item.class + ] + }, + children: [ + { + tag: 'code', + attributes: { + class: ['ck-style-preview-sample'] + }, + children: [ + { + text: item.sample || item.text + } + ] + } + ] + }); + + buttonView.children.add(previewContent); + buttonView.bind('label').to(previewModel, 'label'); + + // 绑定点击事件 + buttonView.on('execute', () => { + dropdown.buttonView.label = item.text; + dropdown.value = item.value; + dropdown.isOpen = false; + + // 触发change事件 + this.fire('change', { + value: item.value, + style: item.class + }); + }); + + listItem.children.add(buttonView); + itemsList.add(listItem); + } + + listView.items.bindTo(itemsList).using(item => item); + dropdown.panelView.children.add(listView); + + return dropdown; + } + _injectStyles() { + const styles = ` + .ck.ck-style-preview.ck-button { + width: 100%; + text-align: left; + padding: 8px; + border: none; + background: none; + } + + .ck.ck-style-preview.ck-button:hover { + background: var(--ck-color-list-button-hover-background); + } + + .ck.ck-style-preview.ck-button.ck-on { + background: var(--ck-color-list-button-on-background); + } + + .ck.ck-style-preview-content { + padding: 8px; + margin: 2px 0; + border-radius: 2px; + } + + .ck-style-preview-sample { + pointer-events: none; + } + + /* 标题样式预览 */ + .ck.ck-style-preview-content.ck-title1 .ck-style-preview-sample { + font-family: '黑体'; + font-size: 29.3px; + font-weight: 'bold'; + } + + .ck.ck-style-preview-content.ck-title2 .ck-style-preview-sample { + font-family: '黑体'; + font-size: 24.4px; + font-weight: 'bold'; + } + + .ck.ck-style-preview-content.ck-title3 .ck-style-preview-sample { + font-family: '宋体'; + font-size: 20.3px; + font-weight: 'bold'; + } + /* 多级标题样式预览 */ + .ck.ck-style-preview-content.ck-h3 .ck-style-preview-sample { + font-size: 1.17em; /* 大约 24px */ + font-weight: bold; + } + .ck.ck-style-preview-content.ck-h4 .ck-style-preview-sample { + + font-size: 1.00em; + font-weight: bold; + } + .ck.ck-style-preview-content.ck-h5 .ck-style-preview-sample { + font-size: 0.83em; + font-weight: bold; + } + + + /* 引用样式预览 */ + .ck.ck-style-preview-content.ck-side-quote .ck-style-preview-sample { + border-left: 4px solid #e74c3c; + padding-left: 16px; + font-style: italic; + } + + .ck.ck-style-preview-content.ck-simple-quote .ck-style-preview-sample { + background: #f5f5f5; + padding: 16px; + border-radius: 4px; + } + + .ck.ck-style-preview-content.ck-dark-quote .ck-style-preview-sample { + background: #2c3e50; + color: white; + padding: 16px; + border-radius: 4px; + } + + /* 正文样式预览 */ + .ck.ck-style-preview-content.ck-normal-text .ck-style-preview-sample { + font-family: '宋体'; + font-size: 16px; + line-height: 1.6; + word-break: break-word; + white-space: pre-wrap; + overflow-wrap: break-word; + text-indent: 2em; + margin: 0; + padding: 0; + } + .ck.ck-style-preview-content.ck-songti-text .ck-style-preview-sample { + font-family: '宋体'; + font-size: 14px; + line-height: 1.6; + word-break: break-word; + white-space: pre-wrap; + overflow-wrap: break-word; + text-indent: 2em; + margin: 0; + padding: 0; + } + .ck.ck-style-preview-content.ck-kaiti-text .ck-style-preview-sample { + font-family: '楷体'; + font-size: 10.5px; + line-height: 1.6; + word-break: break-word; + white-space: pre-wrap; + overflow-wrap: break-word; + text-indent: 2em; + margin: 0; + padding: 0; + } + + /* 块引用样式预览 */ + .ck.ck-style-preview-content.ck-side-quote .ck-style-preview-sample { + font-family: 'Oswald'; + font-style: normal; + float: right; + width: 35%; + position: relative; + border: 0; + overflow: visible; + z-index: 1; + margin-left: 1em; + } + .ck.ck-style-preview-content.ck-side-quote .ck-style-preview-sample::before { + content: '“'; + position: absolute; + top: -37px; + left: -10px; + display: block; + font-size: 200px; + color: #e7e7e7; + z-index: -1; + line-height: 1; + } + .ck.ck-style-preview-content.ck-side-quote .ck-style-preview-sample p{ + font-size: 2em; + line-height: 1; + } + + .ck.ck-style-preview-content.ck-side-quote .ck-style-preview-sample p:last-child:not(:first-child){ + font-size: 1.3em; + text-align: right; + color: #555; + } + /* 代码块样式预览 */ + .ck.ck-style-preview-content.ck-fancy-code { + border: 0; + margin-left: 2em; + margin-right: 2em; + border-radius: 10px; + } + .ck.ck-style-preview-content.ck-fancy-code::before{ + content: ''; + display: block; + height: 13px; + background: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NCAxMyI+CiAgPGNpcmNsZSBjeD0iNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGMzZCNUMiLz4KICA8Y2lyY2xlIGN4PSIyNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGOUJFNEQiLz4KICA8Y2lyY2xlIGN4PSI0Ny41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiM1NkM0NTMiLz4KPC9zdmc+Cg==); + margin-bottom: 8px; + background-repeat: no-repeat; + } + .ck.ck-style-preview-content.ck-fancy-code-dark{ + background: #272822; + color: #fff; + box-shadow: 5px 5px 0 #0000001f; + } + .ck.ck-style-preview-content.ck-fancy-code-bright{ + background: #dddfe0; + color: #000; + box-shadow: 5px 5px 0 #b3b3b3; + } + `; + + // 创建style元素并添加到编辑器容器中 + const styleElement = document.createElement('style'); + styleElement.textContent = styles; + this.element.ownerDocument.head.appendChild(styleElement); + } + + + _createButtons() { + this.saveButtonView = new ButtonView(this.locale); + this.saveButtonView.set({ + label: '保存', + withText: true, + class: 'ck-button-save' + }); + + this.cancelButtonView = new ButtonView(this.locale); + this.cancelButtonView.set({ + label: '取消', + withText: true, + class: 'ck-button-cancel' + }); + } + + _createSection(label, children) { + return { + tag: 'div', + attributes: { + class: ['ck', 'ck-setting-section'] + }, + children: [ + { + tag: 'label', + attributes: { + class: ['ck', 'ck-setting-section-label'] + }, + children: [label] + }, + { + tag: 'div', + attributes: { + class: ['ck', 'ck-setting-section-content'] + }, + children + } + ] + }; + } + + getData() { + return { + titleStyle: { + option: this.titleStyleDropdown.value + }, + headingStyle: { + option: this.headingStyleDropdowns.map(heading => [ + heading.size.value, + heading.style.value + ]) + }, + listStyle: { + option: this.listStyleDropdown.value + }, + bodyStyle: { + option: this.bodyStyleDropdown.value + }, + blockquoteStyle: { + option: this.blockquoteStyleDropdown.value + }, + codeBlockStyle: { + option: this.codeBlockStyleDropdown.value.split(' ') + } + }; + } + + render() { + super.render(); + + // 注入样式 + this._injectStyles(); + + // 阻止表单提交 + this.element.addEventListener('submit', evt => { + evt.preventDefault(); + }); + } + + focus() { + this.titleStyleDropdown.focus(); + } +} +class SettingButton extends Plugin { + static get requires() { + return [ContextualBalloon]; + } + + init() { + console.log('SettingButton plugin initialized'); // 调试日志 + + const editor = this.editor; + this._balloon = editor.plugins.get(ContextualBalloon); + this._formView = new SettingFormView(editor.locale); + + // 注册按钮 + editor.ui.componentFactory.add('SettingButton', () => { + const button = new ButtonView(); + + button.set({ + label: '设置', + icon: '', + tooltip: true + }); + + // 点击按钮时显示表单 + button.on('execute', () => { + console.log('Setting button clicked'); // 调试日志 + this._showForm(); + }); + + return button; + }); + + // 绑定表单事件 + this._formView.saveButtonView.on('execute', () => { + console.log('Saving settings:', this._formView.getData()); + // 保存用户设置 + saveUserAILayoutConfig(this._formView.getData()) + this._hideForm(); + }); + + this._formView.cancelButtonView.on('execute', () => { + console.log('Cancel button clicked'); // 调试日志 + this._hideForm(); + }); + } + + _showForm() { + if (this._isFormInPanel) { + return; + } + + const targetElement = this.editor.ui.getEditableElement(); + const positions = [ + targetElement.getBoundingClientRect().top + window.scrollY, + targetElement.getBoundingClientRect().left + window.scrollX + ]; + + this._balloon.add({ + view: this._formView, + position: { + target: targetElement, + positions: [ + (targetRect, balloonRect) => ({ + top: positions[0] + 10, + left: positions[1] + targetRect.width / 2 - balloonRect.width / 2, + name: 'arrow_n' + }) + ] + } + }); + + this._formView.focus(); + this._isFormInPanel = true; + } + + _hideForm() { + if (!this._isFormInPanel) { + return; + } + + this._balloon.remove(this._formView); + this._isFormInPanel = false; + } + + destroy() { + super.destroy(); + this._formView.destroy(); + } +} + // AI 自动排版 async function aiformat() { console.log("ai formatting") @@ -692,9 +1555,9 @@ class PicRecog extends Plugin { // 配置CKEditor5 // DFZ // -function setConfig() { +async function setConfig() { // 获取用户的样式配置 - const userConfig = getUserConfigFromBackend(); + const userConfig = await getUserConfigFromBackend(); return { toolbar: { items: [ @@ -725,7 +1588,7 @@ function setConfig() { 'numberedList', 'outdent', 'indent', - '|', 'ExportToWord', 'ExportToPDF', 'RefineDoc', 'SideBar', 'SaveButton', 'AiFormat' + '|', 'ExportToWord', 'ExportToPDF', 'RefineDoc', 'SideBar', 'SaveButton', 'AiFormat','SettingButton' ], shouldNotGroupWhenFull: true }, @@ -799,7 +1662,7 @@ function setConfig() { TodoList, Underline, Undo, - Export2Word, RefineDoc, Export2PDF, ToggleSideBar, SaveButton, AiFormat, PicRecog + Export2Word, RefineDoc, Export2PDF, ToggleSideBar, SaveButton,SettingButton, AiFormat, PicRecog ], balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList'], //自定义设置字体 diff --git a/coeditor_frontend/src/components/utils.js b/coeditor_frontend/src/components/utils.js index 9de13e4..ab0a9fd 100644 --- a/coeditor_frontend/src/components/utils.js +++ b/coeditor_frontend/src/components/utils.js @@ -1,87 +1,121 @@ // utils.js import { MarkdownToHtml } from '@ckeditor/ckeditor5-markdown-gfm/src/markdown2html/markdown2html.js'; import { HtmlToMarkdown } from '@ckeditor/ckeditor5-markdown-gfm/src/html2markdown/html2markdown.js'; +import { useStore } from 'vuex'; // 获取用户配置 -export function getUserConfigFromBackend() { - // TODO 请求用户样式 - - - - const options = {}; - // 字体、字号、样式 - const { - fontFamilyOptions = [ - 'default', - '宋体', - '新宋体', - '仿宋', - '楷体', - '微软雅黑', - '黑体', - '华文仿宋', - '华文楷体', - '华文隶书', - '华文宋体', - '华文细黑', - '华文新魏', - '华文行楷', - '华文中宋', - '隶书', - '苹方 常规', - '幼圆', - 'Times New Roman' - ], - // 五号,小四,四号,小三,三号,小二,二号 - fontSizeOptions = [14, 'default', 16, 18.6, 20, 21.3, 24, 29.3], - styleDefinitions = [ - { - name: 'Article category', - element: 'h3', - classes: ['category'] - }, - { - name: 'Title', - element: 'h2', - classes: ['document-title'] - }, - { - name: 'Subtitle', - element: 'h3', - classes: ['document-subtitle'] - }, - { - name: 'Info box', - element: 'p', - classes: ['info-box'] - }, - { - name: 'Side quote', - element: 'blockquote', - classes: ['side-quote'] - }, - { - name: 'Marker', - element: 'span', - classes: ['marker'] - }, - { - name: 'Spoiler', - element: 'span', - classes: ['spoiler'] - }, - { - name: 'Code (dark)', - element: 'pre', - classes: ['fancy-code', 'fancy-code-dark'] - }, - { - name: 'Code (bright)', - element: 'pre', - classes: ['fancy-code', 'fancy-code-bright'] - } - ] - } = options; - // 如果传入的options没有对应项,使用默认值 +// Make the function async so we can use await +export async function getUserConfigFromBackend() { + const fontFamilyOptions = [ + 'default', + '宋体', + '新宋体', + '仿宋', + '楷体', + '微软雅黑', + '黑体', + '华文仿宋', + '华文楷体', + '华文隶书', + '华文宋体', + '华文细黑', + '华文新魏', + '华文行楷', + '华文中宋', + '隶书', + '苹方 常规', + '幼圆', + 'Times New Roman' + ]; + + const fontSizeOptions = [14, 'default', 16, 18.6, 20, 21.3, 24, 29.3]; + + let styleDefinitions = [ + { + name: 'Article category', + element: 'h3', + classes: ['category'] + }, + { + name: 'Title', + element: 'h2', + classes: ['document-title'] + }, + { + name: 'Subtitle', + element: 'h3', + classes: ['document-subtitle'] + }, + { + name: 'Info box', + element: 'p', + classes: ['info-box'] + }, + { + name: 'Side quote', + element: 'blockquote', + classes: ['side-quote'] + }, + { + name: 'Marker', + element: 'span', + classes: ['marker'] + }, + { + name: 'Spoiler', + element: 'span', + classes: ['spoiler'] + }, + { + name: 'Code (dark)', + element: 'pre', + classes: ['fancy-code', 'fancy-code-dark'] + }, + { + name: 'Code (bright)', + element: 'pre', + classes: ['fancy-code', 'fancy-code-bright'] + } + ]; + + try { + const formData = new FormData(); + formData.append('user_name', useStore().state.user.username); + const requestOptions = { + method: 'POST', + body: formData + }; + console.log(requestOptions); + + // 使用 await 等待请求完成 + const response = await fetch("http://localhost:14514/admin/user_config/user_style_get", requestOptions); + + if (!response.ok) { + throw new Error("请求出错"); + } + + const data = await response.json(); + console.log("POST请求成功:", data); + + // 处理返回的样式数据 + data.data.forEach(style => { + styleDefinitions.push({ + name: style.style_name, + element: style.element_name, + classes: style.style_classes + }); + + // 将 style_content 动态插入到页面的 CSS 中 + const styleElement = document.createElement('style'); + styleElement.textContent = style.style_content; + document.head.appendChild(styleElement); + }); + console.log("styleDefinitions: ", styleDefinitions); + + } catch (error) { + console.error("POST请求出错:", error); + } + + console.log("styleDefinitions: ", styleDefinitions); return { fontFamily: { options: fontFamilyOptions @@ -161,45 +195,112 @@ export function html2markdown(htmlString) { } // 请求用户AI生成的样式配置 -export function getUserAILayoutConfig() { - // TODO 请求用户AI生成的样式配置 - const options = {}; - // 对应的样式定义在`generatedStyle.css`中 - const { - // title - titleStyleOption = 'title1', - // 标题样式配置 如 一、二 (1)、(2) 1. 2. I. II. A. B. (一)、(二) - // 每级标题按顺序应用 [标题大小,标题样式] 大小也可自定义 - headingStyleOption = [['h3', 'heading1'], ['h4', 'heading2'], ['h5', 'heading3']], // limit 3 - // 列表样式配置 - listStyleOption = 'square', - // 下面的各项样式每一个对应的均会应用 - // 正文样式配置 小四宋体 - bodyStyleOption = ['normal-text'], - // 块引用样式配置 - blockquoteStyleOption = ['side-quote'], - // 代码块样式配置 - codeBlockStyleOption = ['fancy-code', 'fancy-code-bright'], - } = options; - // 如果传入的options没有对应项,使用默认值 +export async function getUserAILayoutConfig() { + // 设置默认值 + let options = { + titleStyleOption: 'title1', + headingStyleOption: [['h3', 'heading1'], ['h4', 'heading2'], ['h5', 'heading3']], + listStyleOption: 'square', + bodyStyleOption: ['normal-text'], + blockquoteStyleOption: ['side-quote'], + codeBlockStyleOption: ['fancy-code', 'fancy-code-bright'] + }; + + try { + const formData = new FormData(); + formData.append('user_name', window.username); + const requestOptions = { + method: 'POST', + body: formData + }; + console.log(requestOptions); + + // 使用 await 等待请求完成 + const response = await fetch("http://localhost:14514/admin/user_config/ai_format_config_get", requestOptions); + + if (!response.ok) { + throw new Error("请求出错"); + } + + const data = await response.json(); + console.log("POST请求成功:", data); + + // 根据后端返回的数据更新配置 + if (data.title_style) { + options.titleStyleOption = data.title_style; + } + if (data.heading_style) { + options.headingStyleOption = data.heading_style; + } + if (data.list_style) { + options.listStyleOption = data.list_style; + } + if (data.body_style) { + options.bodyStyleOption = [data.body_style]; + } + if (data.blockquote_style) { + options.blockquoteStyleOption = [data.blockquote_style]; + } + if (data.codeblock_style) { + options.codeBlockStyleOption = data.codeblock_style; + } + + } catch (error) { + console.error("POST请求出错:", error); + // 发生错误时使用默认配置 + } + + // 返回配置对象 + console.log('getUserAILayoutConfig', options); return { titleStyle: { - option: titleStyleOption + option: options.titleStyleOption }, headingStyle: { - option: headingStyleOption + option: options.headingStyleOption }, bodyStyle: { - option: bodyStyleOption + option: options.bodyStyleOption }, blockquoteStyle: { - option: blockquoteStyleOption + option: options.blockquoteStyleOption }, codeBlockStyle: { - option: codeBlockStyleOption + option: options.codeBlockStyleOption }, listStyle: { - option: listStyleOption + option: options.listStyleOption } }; +} +export async function saveUserAILayoutConfig(config) { + console.log('saveUserAILayoutConfig', config); + console.log('window.store', window.username); + try { + const formData = new FormData(); + formData.append('user_name', window.username); + formData.append('title_style', config.titleStyle.option); + formData.append('heading_style', config.headingStyle.option); + formData.append('list_style', config.listStyle.option); + formData.append('body_style', config.bodyStyle.option); + formData.append('blockquote_style', config.blockquoteStyle.option); + formData.append('codeblock_style', config.codeBlockStyle.option); + const requestOptions = { + method: 'POST', + body: formData + }; + console.log(requestOptions); + + // 使用 await 等待请求完成 + const response = await fetch("http://localhost:14514/admin/user_config/ai_format_config_save", requestOptions); + + if (!response.ok) { + throw new Error("请求出错"); + } + + const data = await response.json(); + console.log("POST请求成功:", data); + } catch (error) { + console.error("POST请求出错:", error); + } } \ No newline at end of file diff --git a/coeditor_frontend/src/public/generatedStyle.css b/coeditor_frontend/src/public/generatedStyle.css index 231b66b..e6db727 100644 --- a/coeditor_frontend/src/public/generatedStyle.css +++ b/coeditor_frontend/src/public/generatedStyle.css @@ -6,6 +6,16 @@ font-size: 29.3px; font-weight: 'bold'; } +.ck-content .title2{ + font-family: '黑体'; + font-size: 24.4px; + font-weight: 'bold'; +} +.ck-content .title3{ + font-family: '宋体'; + font-size: 20.3px; + font-weight: 'bold'; +} /* heading style */ /* 使用前必须在对应元素的父元素中将对应计数器重置 */ .ck-content .heading1::before{ @@ -202,4 +212,29 @@ text-indent: 2em; margin: 0; padding: 0; +} +/* 四号宋体 */ +.ck-content p.songti-text { + font-family: '宋体'; + font-size: 14px; + line-height: 1.6; + word-break: break-word; + white-space: pre-wrap; + overflow-wrap: break-word; + text-indent: 2em; + margin: 0; + padding: 0; +} + +/* 五号楷体 */ +.ck-content p.kaiti-text { + font-family: '楷体'; + font-size: 10.5px; + line-height: 1.6; + word-break: break-word; + white-space: pre-wrap; + overflow-wrap: break-word; + text-indent: 2em; + margin: 0; + padding: 0; } \ No newline at end of file diff --git a/coeditor_frontend/src/views/CkeditorView.vue b/coeditor_frontend/src/views/CkeditorView.vue index ddb7d7a..4b7a262 100644 --- a/coeditor_frontend/src/views/CkeditorView.vue +++ b/coeditor_frontend/src/views/CkeditorView.vue @@ -16,7 +16,7 @@
-