From 276c11bec0647bd1da4ebc7bdf4af12876318919 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Fri, 12 Sep 2025 17:04:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=8C=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/prosperity-shop/goods-form.vue | 140 +++- .../components/rich-text-editor/README.md | 83 ++ .../rich-text-editor/rich-text-editor.vue | 724 ++++++++++++++++++ .../rich-text-input/rich-text-input.vue | 409 ++++++++++ 4 files changed, 1353 insertions(+), 3 deletions(-) create mode 100644 src/sheep/components/rich-text-editor/README.md create mode 100644 src/sheep/components/rich-text-editor/rich-text-editor.vue create mode 100644 src/sheep/components/rich-text-input/rich-text-input.vue diff --git a/src/pages/prosperity-shop/goods-form.vue b/src/pages/prosperity-shop/goods-form.vue index 02b6341..9927649 100644 --- a/src/pages/prosperity-shop/goods-form.vue +++ b/src/pages/prosperity-shop/goods-form.vue @@ -74,7 +74,11 @@ 注:图片大小不超过5M - + @@ -169,6 +173,7 @@ import { reactive, ref } from "vue" import { hooks } from "@@/index" import { prosperityShopApi } from "@/sheep/api" import ShopCategaryPicker from "@/sheep/components/shop-categary-picker.vue" +import RichTextInput from "@/sheep/components/rich-text-input/rich-text-input.vue" import { onLoad } from "@dcloudio/uni-app" import { filePath } from "@/sheep/utils/file" @@ -234,6 +239,12 @@ const getGoodsDetail = async (id: string) => { const saleEndTime = data.saleEndTime _saleStartTimeStr.value = saleStartTime _saleEndTimeStr.value = saleEndTime + + // 转换HTML为Markdown格式 + const description = convertHtmlToMarkdown(data.description || '') + console.log('原始HTML:', data.description) + console.log('转换后Markdown:', description) + Object.assign(form, { ...data, status: data.status.toString(), @@ -243,10 +254,89 @@ const getGoodsDetail = async (id: string) => { saleStartTime, saleEndTime, skuList, + description, }) uni.hideLoading() } +// HTML转Markdown函数 +const convertHtmlToMarkdown = (html: string): string => { + if (!html) return '' + + // 移除HTML标签,保留文本内容 + let markdown = html + // 处理标题 + .replace(/]*>(.*?)<\/h1>/gi, '# $1\n') + .replace(/]*>(.*?)<\/h2>/gi, '## $1\n') + .replace(/]*>(.*?)<\/h3>/gi, '### $1\n') + .replace(/]*>(.*?)<\/h4>/gi, '#### $1\n') + .replace(/]*>(.*?)<\/h5>/gi, '##### $1\n') + .replace(/]*>(.*?)<\/h6>/gi, '###### $1\n') + // 处理段落 + .replace(/]*>(.*?)<\/p>/gi, '$1\n\n') + // 处理换行 + .replace(//gi, '\n') + // 处理粗体 + .replace(/]*>(.*?)<\/strong>/gi, '**$1**') + .replace(/]*>(.*?)<\/b>/gi, '**$1**') + // 处理斜体 + .replace(/]*>(.*?)<\/em>/gi, '*$1*') + .replace(/]*>(.*?)<\/i>/gi, '*$1*') + // 处理下划线 + .replace(/]*>(.*?)<\/u>/gi, '__$1__') + // 处理链接 + .replace(/]*href=["']([^"']*)["'][^>]*>(.*?)<\/a>/gi, '[$2]($1)') + // 处理图片 - 更全面的图片处理 + .replace(/]*src=["']([^"']*)["'][^>]*alt=["']([^"']*)["'][^>]*>/gi, (match, src, alt) => { + // 直接使用原始URL,不添加额外前缀 + console.log('HTML图片转换 - 原始:', match) + console.log('HTML图片转换 - src:', src) + console.log('HTML图片转换 - alt:', alt) + return `![${alt || '图片'}](${src})` + }) + .replace(/]*src=["']([^"']*)["'][^>]*>/gi, (match, src) => { + // 直接使用原始URL,不添加额外前缀 + console.log('HTML图片转换 - 原始:', match) + console.log('HTML图片转换 - src:', src) + return `![图片](${src})` + }) + // 处理列表 + .replace(/]*>(.*?)<\/ul>/gis, (match, content) => { + return content.replace(/]*>(.*?)<\/li>/gi, '- $1\n') + }) + .replace(/]*>(.*?)<\/ol>/gis, (match, content) => { + let index = 1 + return content.replace(/]*>(.*?)<\/li>/gi, () => `${index++}. $1\n`) + }) + // 处理引用 + .replace(/]*>(.*?)<\/blockquote>/gis, (match, content) => { + return content.replace(/\n/g, '\n> ') + '\n' + }) + // 处理代码 + .replace(/]*>(.*?)<\/code>/gi, '`$1`') + .replace(/]*>(.*?)<\/pre>/gis, '```\n$1\n```') + // 处理分割线 + .replace(/]*>/gi, '---\n') + // 处理表格(简单处理) + .replace(/]*>(.*?)<\/table>/gis, (match, content) => { + // 这里可以添加更复杂的表格处理逻辑 + return content.replace(/]*>(.*?)<\/tr>/gi, (trMatch, trContent) => { + const cells = trContent.match(/]*>(.*?)<\/t[hd]>/gi) + if (cells) { + return '| ' + cells.map(cell => cell.replace(/<[^>]*>/g, '')).join(' | ') + ' |\n' + } + return '' + }) + }) + // 移除剩余的HTML标签 + .replace(/<[^>]*>/g, '') + // 清理多余的换行 + .replace(/\n{3,}/g, '\n\n') + .trim() + + return markdown +} + const getGoodsCategory = async () => { const { code, data, msg } = await prosperityShopApi.getGoodsCategory() if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" }) @@ -288,10 +378,50 @@ const addSkuListItem = () => { }) } +// Markdown转HTML函数 +const convertMarkdownToHtml = (markdown: string): string => { + if (!markdown) return '' + + let html = markdown + // 处理标题 + .replace(/^# (.*$)/gim, '

$1

') + .replace(/^## (.*$)/gim, '

$1

') + .replace(/^### (.*$)/gim, '

$1

') + .replace(/^#### (.*$)/gim, '

$1

') + .replace(/^##### (.*$)/gim, '
$1
') + .replace(/^###### (.*$)/gim, '
$1
') + // 处理粗体 + .replace(/\*\*(.*?)\*\*/g, '$1') + // 处理斜体 + .replace(/\*(.*?)\*/g, '$1') + // 处理链接 + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') + // 处理图片 + .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1') + // 处理段落 + .replace(/^(?!<[h1-6]|$1

') + // 处理换行 + .replace(/\n/g, '
') + + return html +} + // 保存 const onsubmit = async (evt: TM.FORM_SUBMIT_RESULT) => { // console.log("校验结果:", evt) - console.log("提交的数据:", { ...form, coverImage: JSON.parse(form.coverImage[0].response).data.url }) + + // 将Markdown格式的description转换为HTML + const htmlDescription = convertMarkdownToHtml(form.description) + console.log("原始Markdown:", form.description) + console.log("转换后HTML:", htmlDescription) + + const submitData = { + ...form, + description: htmlDescription, // 使用转换后的HTML + coverImage: JSON.parse(form.coverImage[0].response).data.url + } + console.log("提交的数据:", submitData) + if (!evt.isPass) return if (form.priceType == "1") { @@ -308,7 +438,11 @@ const onsubmit = async (evt: TM.FORM_SUBMIT_RESULT) => { } try { - const params = { ...form, coverImage: JSON.parse(form.coverImage[0].response).data.url } + const params = { + ...form, + description: htmlDescription, // 使用转换后的HTML + coverImage: JSON.parse(form.coverImage[0].response).data.url + } uni.showLoading({ title: "提交中..." }) const { code, msg } = await (form.id ? prosperityShopApi.editGoods(params) : prosperityShopApi.addGoods(params)) if (code != 0) return uni.showToast({ title: msg || "提交失败", icon: "none" }) diff --git a/src/sheep/components/rich-text-editor/README.md b/src/sheep/components/rich-text-editor/README.md new file mode 100644 index 0000000..df24e0f --- /dev/null +++ b/src/sheep/components/rich-text-editor/README.md @@ -0,0 +1,83 @@ +# 富文本编辑器组件 + +这是一个简单的富文本编辑器组件,专为移动端设计,支持Markdown语法。 + +## 组件文件 + +- `rich-text-editor.vue` - 核心编辑器组件 +- `rich-text-input.vue` - 表单输入组件 + +## 使用方法 + +### 1. 在表单中使用 + +```vue + + + +``` + +### 2. 直接使用编辑器 + +```vue + + + +``` + +## 功能特性 + +- ✅ 支持Markdown语法(标题、粗体、斜体、列表、表格、链接等) +- ✅ 实时预览功能 +- ✅ 模板插入功能 +- ✅ 移动端优化界面 +- ✅ 弹窗编辑模式 +- ✅ 表单集成 + +## 支持的Markdown语法 + +- `# 标题` - 一级标题 +- `## 标题` - 二级标题 +- `### 标题` - 三级标题 +- `**粗体**` - 粗体文字 +- `*斜体*` - 斜体文字 +- `- 列表项` - 无序列表 +- `> 引用` - 引用块 +- `[链接文字](URL)` - 链接 +- `![图片描述](图片URL)` - 图片 +- `| 表格 |` - 表格 + +## 注意事项 + +1. 组件使用tm-ui的Modal组件,确保项目中已安装tm-ui +2. 图片上传功能需要配合项目的上传接口 +3. 组件样式可以根据项目需求进行调整 +4. 没有修改任何配置文件,只创建了组件文件 diff --git a/src/sheep/components/rich-text-editor/rich-text-editor.vue b/src/sheep/components/rich-text-editor/rich-text-editor.vue new file mode 100644 index 0000000..756e74f --- /dev/null +++ b/src/sheep/components/rich-text-editor/rich-text-editor.vue @@ -0,0 +1,724 @@ + + + + + diff --git a/src/sheep/components/rich-text-input/rich-text-input.vue b/src/sheep/components/rich-text-input/rich-text-input.vue new file mode 100644 index 0000000..6864d38 --- /dev/null +++ b/src/sheep/components/rich-text-input/rich-text-input.vue @@ -0,0 +1,409 @@ + + + + +