为了调试Element UI 的TinyMCE编辑器,给它配上更强大的 135编辑器,我特意让Claude 用Python做了个文章管理系统,数据库用的mysql,同时为了测试效果,又让它生成了一个移动端的前端用来查看文章效果。
Claude还是采用➡️ Windows下用docker desktop搭建Claude code 一文中的方式!
- 给出 Prompt,自动生成 Todos
Update Todos
☐ 分析当前项目结构,了解TinyMCE 4.7.5配置 ☐ 创建Vue2 + Element UI的文章管理后台前端 ☐ 在TinyMCE中集成135编辑器功能 ☐ 创建Python后端API(Flask/Django) ☐ 创建MySQL数据库和文章表结构 ☐ 实现文章的增删改查功能 ☐ 创建移动端展示页面
- 略去中间battle部分
- 先看效果图
- 把135编辑器做到了tinymce内部
- 生成的代码,内置的template 还是过于简陋!
- 支持效果预览
mobile 文章列表页,AI最爱的紫色
mobile详情页 紫色+1
- 完整核心代码奉上
<template>
<div class="tinymce-editor">
<div :id="editorId" class="tinymce-container"></div>
<!-- 135编辑器模态框 -->
<el-dialog
title="135编辑器"
:visible.sync="editor135Visible"
width="90%"
top="5vh"
custom-class="editor-135-dialog"
>
<div class="editor-135-container">
<div class="editor-135-sidebar">
<div class="sidebar-header">
<h3>135编辑器</h3>
<el-input
v-model="searchKeyword"
placeholder="搜索模板..."
size="small"
clearable
suffix-icon="el-icon-search"
@input="filterTemplates"
/>
</div>
<el-menu
:default-active="currentCategory"
@select="selectTemplateCategory"
class="category-menu"
>
<el-menu-item index="hot">
<i class="el-icon-star-on"></i>
<span>热门推荐</span>
<el-badge :value="templates.hot.length" class="category-badge"/>
</el-menu-item>
<el-menu-item index="title">
<i class="el-icon-s-flag"></i>
<span>标题样式</span>
<el-badge :value="templates.title.length" class="category-badge"/>
</el-menu-item>
<el-menu-item index="text">
<i class="el-icon-document"></i>
<span>正文排版</span>
<el-badge :value="templates.text.length" class="category-badge"/>
</el-menu-item>
<el-menu-item index="quote">
<i class="el-icon-chat-dot-square"></i>
<span>引言引用</span>
<el-badge :value="templates.quote.length" class="category-badge"/>
</el-menu-item>
<el-menu-item index="image">
<i class="el-icon-picture"></i>
<span>图文混排</span>
<el-badge :value="templates.image.length" class="category-badge"/>
</el-menu-item>
<el-menu-item index="list">
<i class="el-icon-menu"></i>
<span>列表样式</span>
<el-badge :value="templates.list.length" class="category-badge"/>
</el-menu-item>
<el-menu-item index="card">
<i class="el-icon-postcard"></i>
<span>卡片样式</span>
<el-badge :value="templates.card.length" class="category-badge"/>
</el-menu-item>
<el-menu-item index="separator">
<i class="el-icon-minus"></i>
<span>分割装饰</span>
<el-badge :value="templates.separator.length" class="category-badge"/>
</el-menu-item>
<el-menu-item index="button">
<i class="el-icon-link"></i>
<span>按钮链接</span>
<el-badge :value="templates.button.length" class="category-badge"/>
</el-menu-item>
</el-menu>
<div class="template-stats">
<p>共 {{ totalTemplates }} 个模板</p>
<p>已筛选 {{ filteredTemplates.length }} 个</p>
</div>
</div>
<div class="editor-135-templates">
<div class="templates-header">
<h4>{{ categoryNames[currentCategory] }}</h4>
<div class="view-controls">
<el-button-group size="mini">
<el-button
:type="viewMode === 'grid' ? 'primary' : ''"
icon="el-icon-s-grid"
@click="viewMode = 'grid'"
>
网格
</el-button>
<el-button
:type="viewMode === 'list' ? 'primary' : ''"
icon="el-icon-s-order"
@click="viewMode = 'list'"
>
列表
</el-button>
</el-button-group>
</div>
</div>
<div v-if="filteredTemplates.length === 0" class="empty-templates">
<div class="empty-content">
<i class="el-icon-search"></i>
<p>没有找到匹配的模板</p>
<p>试试其他关键词或选择其他分类</p>
</div>
</div>
<div v-else :class="['template-container', viewMode]">
<div
v-for="template in filteredTemplates"
:key="template.id"
:class="['template-item', viewMode]"
@click="insertTemplate(template)"
>
<div class="template-preview">
<div class="preview-content" v-html="template.html"></div>
<div class="template-overlay">
<div class="overlay-actions">
<el-button size="small" type="primary" icon="el-icon-plus">
使用模板
</el-button>
<el-button size="small" icon="el-icon-view" @click.stop="previewLargeTemplate(template)">
预览
</el-button>
</div>
</div>
</div>
<div class="template-info">
<div class="template-title">{{ template.name }}</div>
<div class="template-description">{{ template.description }}</div>
<div class="template-tags">
<span
v-for="tag in template.tags.slice(0, 3)"
:key="tag"
class="tag"
>
{{ tag }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div slot="footer">
<el-button @click="editor135Visible = false">取消</el-button>
</div>
</el-dialog>
<!-- 大预览模态框 -->
<el-dialog
title="模板预览"
:visible.sync="largePreviewVisible"
width="60%"
center
>
<div class="large-preview-container">
<div class="large-preview-content" v-html="largePreviewTemplate?.html"></div>
</div>
<div slot="footer">
<el-button @click="largePreviewVisible = false">关闭</el-button>
<el-button type="primary" @click="insertTemplateFromPreview">
使用此模板
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { articleApi } from '@/api/articles'
export default {
name: 'TinyMCEEditor',
props: {
value: {
type: String,
default: ''
},
height: {
type: Number,
default: 400
}
},
data() {
return {
editor: null,
editorId: 'tinymce-' + Date.now(),
editor135Visible: false,
currentCategory: 'hot',
searchKeyword: '',
viewMode: 'grid',
previewTemplate: null,
largePreviewVisible: false,
largePreviewTemplate: null,
categoryNames: {
hot: '热门推荐',
title: '标题样式',
text: '正文排版',
quote: '引言引用',
image: '图文混排',
list: '列表样式',
card: '卡片样式',
separator: '分割装饰',
button: '按钮链接'
},
templates: {
hot: [
{
id: 'hot1',
name: '科技感标题',
description: '适合科技、互联网类文章',
tags: ['科技', '现代', '渐变'],
html: '<section style="margin: 20px auto; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; text-align: center;"><h2 style="color: white; font-size: 24px; margin: 0; text-shadow: 0 2px 4px rgba(0,0,0,0.3);">🚀 创新科技标题</h2></section>'
},
{
id: 'hot2',
name: '重点提示框',
description: '突出重要信息',
tags: ['提示', '重要', '醒目'],
html: '<section style="margin: 20px auto; padding: 15px 20px; background: #fff3cd; border: 2px solid #ffc107; border-radius: 8px; position: relative;"><div style="position: absolute; top: -10px; left: 20px; background: #ffc107; color: #856404; padding: 5px 15px; border-radius: 15px; font-size: 12px; font-weight: bold;">💡 重要提示</div><p style="margin: 10px 0 0 0; color: #856404; line-height: 1.6;">这里是需要特别注意的重要信息内容...</p></section>'
},
{
id: 'hot3',
name: '对话问答',
description: '模拟对话形式的问答',
tags: ['对话', '问答', 'Q&A'],
html: '<section style="margin: 20px auto;"><div style="margin-bottom: 15px; padding: 12px 18px; background: #e3f2fd; border-radius: 18px 18px 18px 5px; position: relative;"><strong style="color: #1976d2;">Q: </strong><span style="color: #333;">这是一个常见问题?</span></div><div style="padding: 12px 18px; background: #f5f5f5; border-radius: 18px 18px 5px 18px; margin-left: 30px;"><strong style="color: #388e3c;">A: </strong><span style="color: #333;">这是对应的详细回答内容...</span></div></section>'
},
{
id: 'hot4',
name: '数据统计卡',
description: '展示数据和统计信息',
tags: ['数据', '统计', '专业'],
html: '<section style="margin: 20px auto; display: flex; justify-content: space-around; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 25px; border-radius: 15px; color: white;"><div style="text-align: center;"><div style="font-size: 32px; font-weight: bold; margin-bottom: 5px;">99%</div><div style="font-size: 14px; opacity: 0.9;">用户满意度</div></div><div style="text-align: center;"><div style="font-size: 32px; font-weight: bold; margin-bottom: 5px;">10W+</div><div style="font-size: 14px; opacity: 0.9;">活跃用户</div></div><div style="text-align: center;"><div style="font-size: 32px; font-weight: bold; margin-bottom: 5px;">24H</div><div style="font-size: 14px; opacity: 0.9;">响应时间</div></div></section>'
},
{
id: 'hot5',
name: '时间轴',
description: '展示发展历程或步骤',
tags: ['时间轴', '流程', '历程'],
html: '<section style="margin: 20px auto; position: relative; padding-left: 30px;"><div style="position: absolute; left: 10px; top: 0; bottom: 0; width: 2px; background: #e1e8ed;"></div><div style="position: relative; margin-bottom: 30px;"><div style="position: absolute; left: -25px; top: 5px; width: 12px; height: 12px; background: #1da1f2; border-radius: 50%; border: 3px solid white; box-shadow: 0 0 0 2px #e1e8ed;"></div><div style="font-weight: bold; color: #333; margin-bottom: 5px;">2024年3月</div><div style="color: #666; line-height: 1.6;">项目启动,完成需求分析和技术选型</div></div><div style="position: relative; margin-bottom: 30px;"><div style="position: absolute; left: -25px; top: 5px; width: 12px; height: 12px; background: #17bf63; border-radius: 50%; border: 3px solid white; box-shadow: 0 0 0 2px #e1e8ed;"></div><div style="font-weight: bold; color: #333; margin-bottom: 5px;">2024年4月</div><div style="color: #666; line-height: 1.6;">完成核心功能开发和测试</div></div></section>'
}
],
title: [
{
id: 'title1',
name: '居中标题',
description: '经典居中标题样式',
tags: ['居中', '简约', '经典'],
html: '<section style="margin: 20px auto; text-align: center;"><h2 style="font-size: 20px; color: #333; margin: 0; padding: 10px 0; border-bottom: 2px solid #007cff; display: inline-block;">在此输入标题</h2></section>'
},
{
id: 'title2',
name: '渐变标题',
description: '彩色渐变效果标题',
tags: ['渐变', '彩色', '现代'],
html: '<section style="margin: 20px auto;"><h2 style="background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 24px; font-weight: bold; margin: 0;">渐变标题文字</h2></section>'
},
{
id: 'title3',
name: '阴影标题',
description: '带阴影效果的立体标题',
tags: ['阴影', '立体', '醒目'],
html: '<section style="margin: 20px auto;"><h2 style="font-size: 26px; color: #2c3e50; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); margin: 0; font-weight: bold;">立体阴影标题</h2></section>'
},
{
id: 'title4',
name: '左侧装饰标题',
description: '左侧带装饰线的标题',
tags: ['装饰', '左对齐', '简洁'],
html: '<section style="margin: 20px auto; display: flex; align-items: center;"><div style="width: 4px; height: 24px; background: linear-gradient(to bottom, #ff6b6b, #4ecdc4); margin-right: 12px; border-radius: 2px;"></div><h2 style="margin: 0; font-size: 20px; color: #2c3e50;">装饰线标题</h2></section>'
},
{
id: 'title5',
name: '背景标题',
description: '带背景色的标题样式',
tags: ['背景', '醒目', '色块'],
html: '<section style="margin: 20px auto;"><h2 style="background: #3498db; color: white; padding: 12px 20px; margin: 0; border-radius: 8px; font-size: 18px; text-align: center;">背景色标题</h2></section>'
},
{
id: 'title6',
name: '霓虹灯效果',
description: '发光霓虹灯文字效果',
tags: ['霓虹', '发光', '炫酷'],
html: '<section style="margin: 20px auto; text-align: center; background: #1a1a2e; padding: 30px; border-radius: 15px;"><h2 style="margin: 0; font-size: 28px; color: #00d4ff; text-shadow: 0 0 5px #00d4ff, 0 0 10px #00d4ff, 0 0 20px #00d4ff, 0 0 40px #00d4ff;">霓虹标题效果</h2></section>'
},
{
id: 'title7',
name: '手写风格',
description: '模拟手写字体的标题',
tags: ['手写', '个性', '创意'],
html: '<section style="margin: 20px auto; text-align: center;"><h2 style="margin: 0; font-family: cursive; font-size: 26px; color: #2c3e50; transform: rotate(-1deg); position: relative;">手写风格标题</h2><div style="width: 60px; height: 3px; background: #e74c3c; margin: 10px auto; transform: rotate(-1deg);"></div></section>'
}
],
text: [
{
id: 'text1',
name: '简约正文',
description: '基础正文排版样式',
tags: ['简约', '基础', '正文'],
html: '<section style="margin: 15px auto; font-size: 16px; color: #333; line-height: 1.8;"><p style="text-indent: 2em; margin: 0;">在这里输入你的正文内容,支持首行缩进的标准排版格式,让文章看起来更加专业和美观。</p></section>'
},
{
id: 'text2',
name: '首字下沉',
description: '杂志风格首字下沉',
tags: ['首字下沉', '杂志', '经典'],
html: '<section style="margin: 20px auto; font-size: 16px; line-height: 1.8; color: #333;"><p style="margin: 0;"><span style="float: left; font-size: 48px; line-height: 42px; margin: 0 8px 0 0; color: #e74c3c; font-weight: bold;">首</span>字下沉是一种经典的排版方式,常见于杂志和报纸中,能够很好地吸引读者的注意力,让文章开头更加引人入胜。</p></section>'
},
{
id: 'text3',
name: '双栏排版',
description: '报纸风格双栏布局',
tags: ['双栏', '报纸', '布局'],
html: '<section style="margin: 20px auto; display: flex; gap: 20px; font-size: 14px; line-height: 1.6; color: #333;"><div style="flex: 1;"><p style="margin: 0;">这是左侧栏的内容,采用双栏排版可以让大段文字的阅读体验更好,特别适合长文章的排版设计。</p></div><div style="width: 1px; background: #ddd;"></div><div style="flex: 1;"><p style="margin: 0;">这是右侧栏的内容,双栏布局在报纸和杂志中很常见,能够充分利用版面空间,提高信息密度。</p></div></section>'
}
],
quote: [
{
id: 'quote1',
name: '左侧引用',
description: '经典左侧线条引用样式',
tags: ['引用', '左侧线', '经典'],
html: '<section style="margin: 20px auto; padding: 15px 20px; background: #f8f9fa; border-left: 4px solid #007cff; font-style: italic; color: #555;"><p style="margin: 0; line-height: 1.6;">"这是一段引用文字,可以用来突出重要的观点或者引用名人名言。"</p><div style="text-align: right; margin-top: 10px; font-size: 14px;">—— 引用来源</div></section>'
},
{
id: 'quote2',
name: '居中引言',
description: '居中显示的引言样式',
tags: ['引言', '居中', '突出'],
html: '<section style="margin: 30px auto; text-align: center; padding: 25px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); border-radius: 10px;"><div style="font-size: 24px; color: #3498db; margin-bottom: 10px;">"</div><p style="font-size: 18px; font-style: italic; color: #2c3e50; margin: 0; line-height: 1.6;">成功不是终点,失败不是致命的,重要的是继续前进的勇气。</p><div style="margin-top: 15px; font-size: 14px; color: #7f8c8d;">—— 温斯顿·丘吉尔</div></section>'
},
{
id: 'quote3',
name: '对话气泡',
description: '聊天对话风格的引用',
tags: ['对话', '气泡', '现代'],
html: '<section style="margin: 20px auto;"><div style="max-width: 80%; margin-left: auto; padding: 12px 18px; background: #007cff; color: white; border-radius: 18px 18px 5px 18px; margin-bottom: 10px;"><p style="margin: 0;">这是一个对话气泡样式的文本框</p></div><div style="max-width: 80%; padding: 12px 18px; background: #e9ecef; border-radius: 18px 18px 18px 5px;"><p style="margin: 0;">可以模拟聊天对话的效果</p></div></section>'
}
],
image: [
{
id: 'image1',
name: '左图右文',
description: '图片在左,文字在右',
tags: ['左图右文', '图文', '布局'],
html: '<section style="display: flex; margin: 20px auto; align-items: center; gap: 15px; padding: 15px; border: 1px solid #e9ecef; border-radius: 8px;"><img src="https://via.placeholder.com/150x100?text=图片" style="width: 150px; height: 100px; border-radius: 8px; object-fit: cover;"><div style="flex: 1;"><h3 style="margin: 0 0 10px 0; font-size: 18px; color: #2c3e50;">图文标题</h3><p style="margin: 0; color: #666; line-height: 1.5;">这里是图文描述内容,可以详细介绍图片相关的信息和内容...</p></div></section>'
},
{
id: 'image2',
name: '上图下文',
description: '图片在上,文字在下',
tags: ['上图下文', '居中', '简洁'],
html: '<section style="margin: 20px auto; text-align: center; max-width: 400px;"><img src="https://via.placeholder.com/350x200?text=精美图片" style="width: 100%; border-radius: 8px; margin-bottom: 15px;"><h3 style="margin: 0 0 8px 0; font-size: 16px; color: #2c3e50;">图片标题</h3><p style="margin: 0; font-size: 14px; color: #888; line-height: 1.4;">图片描述文字,简要说明图片内容</p></section>'
},
{
id: 'image3',
name: '圆形头像介绍',
description: '适合人物介绍的样式',
tags: ['人物', '头像', '介绍'],
html: '<section style="display: flex; margin: 20px auto; align-items: center; padding: 20px; background: #f8f9fa; border-radius: 12px; gap: 20px;"><img src="https://via.placeholder.com/80x80?text=头像" style="width: 80px; height: 80px; border-radius: 50%; object-fit: cover;"><div><h3 style="margin: 0 0 8px 0; color: #2c3e50; font-size: 18px;">张三</h3><p style="margin: 0; color: #666; line-height: 1.5;">资深产品经理,拥有10年互联网行业经验,专注于用户体验设计和产品创新。</p></div></section>'
}
],
list: [
{
id: 'list1',
name: '数字列表',
description: '带数字标号的列表',
tags: ['数字', '有序', '列表'],
html: '<section style="margin: 20px auto;"><div style="display: flex; align-items: flex-start; margin-bottom: 15px;"><div style="background: #3498db; color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; margin-right: 12px; flex-shrink: 0;">1</div><p style="margin: 0; line-height: 1.6; color: #333;">第一个要点内容,可以是重要信息或步骤</p></div><div style="display: flex; align-items: flex-start; margin-bottom: 15px;"><div style="background: #3498db; color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; margin-right: 12px; flex-shrink: 0;">2</div><p style="margin: 0; line-height: 1.6; color: #333;">第二个要点内容,保持格式的一致性</p></div><div style="display: flex; align-items: flex-start;"><div style="background: #3498db; color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; margin-right: 12px; flex-shrink: 0;">3</div><p style="margin: 0; line-height: 1.6; color: #333;">第三个要点内容,清晰易读</p></div></section>'
},
{
id: 'list2',
name: '图标列表',
description: '使用图标的要点列表',
tags: ['图标', '要点', '美观'],
html: '<section style="margin: 20px auto;"><div style="display: flex; align-items: center; margin-bottom: 12px; padding: 10px; background: #f8f9fa; border-radius: 6px;"><span style="margin-right: 10px; font-size: 16px;">✅</span><span style="color: #333; line-height: 1.5;">功能特点一:简单易用的操作界面</span></div><div style="display: flex; align-items: center; margin-bottom: 12px; padding: 10px; background: #f8f9fa; border-radius: 6px;"><span style="margin-right: 10px; font-size: 16px;">✅</span><span style="color: #333; line-height: 1.5;">功能特点二:强大的自定义选项</span></div><div style="display: flex; align-items: center; padding: 10px; background: #f8f9fa; border-radius: 6px;"><span style="margin-right: 10px; font-size: 16px;">✅</span><span style="color: #333; line-height: 1.5;">功能特点三:完善的技术支持</span></div></section>'
},
{
id: 'list3',
name: '步骤清单',
description: '操作步骤的清单样式',
tags: ['步骤', '流程', '教程'],
html: '<section style="margin: 20px auto;"><div style="position: relative; padding-left: 30px; margin-bottom: 20px;"><div style="position: absolute; left: 0; top: 0; width: 20px; height: 20px; background: #27ae60; border-radius: 50%; color: white; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold;">1</div><div style="margin-left: 10px;"><h4 style="margin: 0 0 5px 0; color: #2c3e50;">第一步</h4><p style="margin: 0; color: #666; font-size: 14px;">详细描述第一步需要进行的操作</p></div><div style="position: absolute; left: 9px; top: 25px; width: 2px; height: 20px; background: #bdc3c7;"></div></div><div style="position: relative; padding-left: 30px; margin-bottom: 20px;"><div style="position: absolute; left: 0; top: 0; width: 20px; height: 20px; background: #f39c12; border-radius: 50%; color: white; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold;">2</div><div style="margin-left: 10px;"><h4 style="margin: 0 0 5px 0; color: #2c3e50;">第二步</h4><p style="margin: 0; color: #666; font-size: 14px;">详细描述第二步需要进行的操作</p></div></div></section>'
}
],
card: [
{
id: 'card1',
name: '信息卡片',
description: '展示信息的卡片样式',
tags: ['信息', '卡片', '整洁'],
html: '<section style="margin: 20px auto; max-width: 400px;"><div style="background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden;"><div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; color: white;"><h3 style="margin: 0 0 8px 0; font-size: 18px;">卡片标题</h3><p style="margin: 0; font-size: 14px; opacity: 0.9;">副标题或简短描述</p></div><div style="padding: 20px;"><p style="margin: 0; color: #333; line-height: 1.6;">这里是卡片的主要内容,可以包含详细的信息描述...</p></div></div></section>'
},
{
id: 'card2',
name: '价格卡片',
description: '商品或服务价格展示',
tags: ['价格', '商品', '突出'],
html: '<section style="margin: 20px auto; max-width: 300px;"><div style="border: 2px solid #3498db; border-radius: 12px; text-align: center; overflow: hidden; background: white;"><div style="background: #3498db; color: white; padding: 15px;"><h3 style="margin: 0; font-size: 18px;">基础版</h3></div><div style="padding: 30px 20px;"><div style="font-size: 36px; font-weight: bold; color: #2c3e50; margin-bottom: 10px;">¥99<span style="font-size: 16px; color: #7f8c8d;">/月</span></div><ul style="list-style: none; padding: 0; margin: 20px 0; text-align: left;"><li style="padding: 5px 0; color: #333;">✓ 功能特性一</li><li style="padding: 5px 0; color: #333;">✓ 功能特性二</li><li style="padding: 5px 0; color: #333;">✓ 功能特性三</li></ul></div></div></section>'
},
{
id: 'card3',
name: '团队成员卡片',
description: '展示团队成员信息',
tags: ['团队', '成员', '介绍'],
html: '<section style="margin: 20px auto; max-width: 250px;"><div style="background: white; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; padding: 25px;"><img src="https://via.placeholder.com/80x80?text=头像" style="width: 80px; height: 80px; border-radius: 50%; margin-bottom: 15px;"><h3 style="margin: 0 0 5px 0; color: #2c3e50; font-size: 18px;">李四</h3><p style="margin: 0 0 10px 0; color: #3498db; font-size: 14px;">前端工程师</p><p style="margin: 0; color: #7f8c8d; font-size: 13px; line-height: 1.4;">专注于React和Vue.js开发,5年工作经验</p></div></section>'
}
],
separator: [
{
id: 'sep1',
name: '虚线分割',
description: '简单的虚线分割线',
tags: ['虚线', '简单', '分割'],
html: '<section style="margin: 30px auto; text-align: center;"><div style="border-top: 2px dashed #ddd; width: 100%;"></div></section>'
},
{
id: 'sep2',
name: '星形分割',
description: '装饰性星形分割',
tags: ['星形', '装饰', '美观'],
html: '<section style="margin: 30px auto; text-align: center;"><div style="font-size: 18px; color: #3498db;">✦ ✦ ✦</div></section>'
},
{
id: 'sep3',
name: '渐变分割线',
description: '彩色渐变效果分割线',
tags: ['渐变', '彩色', '现代'],
html: '<section style="margin: 30px auto;"><div style="height: 2px; background: linear-gradient(90deg, transparent 0%, #667eea 50%, transparent 100%);"></div></section>'
},
{
id: 'sep4',
name: '文字分割',
description: '带文字的装饰分割线',
tags: ['文字', '装饰', '优雅'],
html: '<section style="margin: 30px auto; text-align: center; position: relative;"><div style="border-top: 1px solid #ddd;"></div><span style="background: white; padding: 0 15px; color: #999; font-size: 14px; position: absolute; top: -8px; left: 50%; transform: translateX(-50%);">— 分割线 —</span></section>'
}
],
button: [
{
id: 'btn1',
name: '立体按钮',
description: '3D立体效果按钮',
tags: ['立体', '3D', '醒目'],
html: '<section style="margin: 20px auto; text-align: center;"><a href="#" style="display: inline-block; padding: 12px 30px; background: linear-gradient(45deg, #3498db, #2980b9); color: white; text-decoration: none; border-radius: 25px; font-weight: bold; box-shadow: 0 4px 10px rgba(52, 152, 219, 0.3); transition: all 0.3s ease; transform: translateY(0);" onmouseover="this.style.transform=\'translateY(-2px)\'; this.style.boxShadow=\'0 6px 15px rgba(52, 152, 219, 0.4)\';" onmouseout="this.style.transform=\'translateY(0)\'; this.style.boxShadow=\'0 4px 10px rgba(52, 152, 219, 0.3)\';">点击按钮</a></section>'
},
{
id: 'btn2',
name: '边框按钮',
description: '简约边框样式按钮',
tags: ['边框', '简约', '轻量'],
html: '<section style="margin: 20px auto; text-align: center;"><a href="#" style="display: inline-block; padding: 10px 25px; border: 2px solid #3498db; color: #3498db; text-decoration: none; border-radius: 5px; font-weight: 500; transition: all 0.3s ease;" onmouseover="this.style.background=\'#3498db\'; this.style.color=\'white\';" onmouseout="this.style.background=\'transparent\'; this.style.color=\'#3498db\';">了解更多</a></section>'
},
{
id: 'btn3',
name: '圆角按钮组',
description: '多个按钮的组合样式',
tags: ['按钮组', '圆角', '组合'],
html: '<section style="margin: 20px auto; text-align: center; display: flex; justify-content: center; gap: 10px; flex-wrap: wrap;"><a href="#" style="display: inline-block; padding: 8px 20px; background: #e74c3c; color: white; text-decoration: none; border-radius: 20px; font-size: 14px;">主要操作</a><a href="#" style="display: inline-block; padding: 8px 20px; background: #95a5a6; color: white; text-decoration: none; border-radius: 20px; font-size: 14px;">次要操作</a><a href="#" style="display: inline-block; padding: 8px 20px; background: #27ae60; color: white; text-decoration: none; border-radius: 20px; font-size: 14px;">确认操作</a></section>'
}
]
}
}
},
computed: {
currentTemplates() {
const templates = this.templates[this.currentCategory] || []
console.log('Current category:', this.currentCategory, 'Templates:', templates)
return templates
},
totalTemplates() {
return Object.values(this.templates).reduce((total, category) => total + category.length, 0)
},
filteredTemplates() {
const current = this.currentTemplates
if (!this.searchKeyword) {
console.log('No search keyword, returning current templates:', current)
return current
}
const filtered = current.filter(template =>
template.name.toLowerCase().includes(this.searchKeyword.toLowerCase()) ||
template.description.toLowerCase().includes(this.searchKeyword.toLowerCase()) ||
template.tags.some(tag => tag.toLowerCase().includes(this.searchKeyword.toLowerCase()))
)
console.log('Filtered templates:', filtered)
return filtered
}
},
mounted() {
this.initTinyMCE()
console.log('Component mounted, templates:', this.templates)
console.log('Current category:', this.currentCategory)
console.log('Current templates:', this.currentTemplates)
},
beforeDestroy() {
if (this.editor) {
this.editor.destroy()
}
},
methods: {
initTinyMCE() {
const self = this
tinymce.init({
selector: `#${this.editorId}`,
height: this.height,
menubar: false,
branding: false,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste code help wordcount'
],
toolbar: 'undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help | code | custom135editor | customupload',
content_style: 'body { font-family: "Microsoft YaHei", Arial, sans-serif; font-size: 14px; }',
setup: function(editor) {
self.editor = editor
// TinyMCE 4.x 使用不同的API添加按钮
editor.addButton('custom135editor', {
text: '135',
tooltip: '135编辑器模板',
onclick: function() {
self.open135Editor()
}
})
// 添加上传图片按钮
editor.addButton('customupload', {
icon: 'image',
tooltip: '上传图片',
onclick: function() {
self.uploadImage()
}
})
editor.on('init', function() {
editor.setContent(self.value || '')
})
editor.on('change keyup', function() {
const content = editor.getContent()
self.$emit('input', content)
})
},
images_upload_handler: function(blobInfo, success, failure) {
self.handleImageUpload(blobInfo, success, failure)
}
})
},
async handleImageUpload(blobInfo, success, failure) {
try {
const formData = new FormData()
formData.append('image', blobInfo.blob(), blobInfo.filename())
const response = await articleApi.uploadImage(formData)
success(response.data.url)
} catch (error) {
failure('图片上传失败')
this.$message.error('图片上传失败')
}
},
open135Editor() {
this.editor135Visible = true
},
uploadImage() {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.onchange = async (e) => {
const file = e.target.files[0]
if (file) {
const formData = new FormData()
formData.append('image', file)
try {
const response = await articleApi.uploadImage(formData)
const imageHtml = `<img src="${response.data.url}" alt="uploaded image" style="max-width: 100%;">`
this.editor.insertContent(imageHtml)
this.$message.success('图片上传成功')
} catch (error) {
this.$message.error('图片上传失败')
}
}
}
input.click()
},
selectTemplateCategory(category) {
this.currentCategory = category
this.searchKeyword = '' // 切换分类时清空搜索
},
insertTemplate(template) {
if (this.editor) {
this.editor.insertContent(template.html)
this.editor135Visible = false
this.$message.success(`模板"${template.name}"插入成功`)
}
},
filterTemplates() {
// 搜索功能,由computed属性filteredTemplates自动处理
},
previewLargeTemplate(template) {
this.largePreviewTemplate = template
this.largePreviewVisible = true
},
insertTemplateFromPreview() {
if (this.largePreviewTemplate && this.editor) {
this.editor.insertContent(this.largePreviewTemplate.html)
this.largePreviewVisible = false
this.editor135Visible = false
this.$message.success(`模板"${this.largePreviewTemplate.name}"插入成功`)
}
},
getContent() {
return this.editor ? this.editor.getContent() : ''
},
setContent(content) {
if (this.editor) {
this.editor.setContent(content || '')
}
}
}
}
</script>
其实有能力的话呀,自己可以持续丰富这个模板,做一些素材小图之类的。
frontend 启动
npm run serve
mobile端 启动命令
python -m http.server 8081 --bind 0.0.0.0
最后附上 CLAUDE.md
Project Overview
This is a comprehensive article management system built for WeChat-style content creation with three main components:
- Frontend: Vue 2 + Element UI admin dashboard for article CRUD operations
- Backend: Flask API server with SQLAlchemy ORM and image upload support
- Mobile: Responsive HTML/CSS/JS mobile article display with dedicated detail pages
- Core Feature: TinyMCE 4.7.5 editor integrated with 50+ custom 135 Editor templates for professional WeChat-style content creation
Development Commands
Database Setup
Windows:
init-db.bat # Interactive database initialization with error handling
fix-charset.bat # Fix MySQL charset issues (MySQL 5.6 compatibility)
test-charset.bat # Test database connection and charset
Linux/macOS:
./init-db.sh # Interactive database setup
Backend Development
Windows:
start-backend.bat # Auto-installs dependencies and runs on localhost:5000
test-backend.bat # Test backend API endpoints
Manual:
cd backend
pip install -r requirements.txt
python app.py # Runs with diagnostic output and error handling
Frontend Development
Windows:
start-frontend.bat # Auto-installs dependencies and runs on localhost:8080
restart-frontend.bat # Force restart frontend service
Manual:
cd frontend
npm install
npm run serve # Development server on localhost:8080
npm run build # Production build
npm run lint # ESLint code linting
Mobile Development
Windows:
start-mobile.bat # HTTP server on localhost:8081 with firewall config
Manual:
cd mobile
python -m http.server 8081 --bind 0.0.0.0
System Management
Windows:
start.bat # Starts both backend and frontend services
stop.bat # Stops all Node.js and Python services
quick-start.bat # One-click setup with enhanced features
check-services.bat # Verify all services are running
diagnose.bat # System diagnostic and troubleshooting
Architecture
135 Editor Integration (Core Feature)
- Location:
frontend/src/components/TinyMCEEditor.vue
- Template System: 50+ professional templates across 9 categories:
hot
: 热门推荐 (featured templates with modern designs)title
: 标题样式 (gradients, shadows, decorative titles)text
: 正文排版 (body text layouts, drop caps)quote
: 引言引用 (blockquotes, chat bubbles)image
: 图文混排 (image-text layouts, profile cards)list
: 列表样式 (numbered, icon, step lists)card
: 卡片样式 (info cards, pricing cards, team cards)separator
: 分割装饰 (dividers, decorative elements)button
: 按钮链接 (CTA buttons, button groups)
- Integration: Custom TinyMCE 4.x toolbar button using
editor.addButton()
API - Features: Search, filter, grid/list view, template preview, responsive design
Backend Architecture
- Main File:
backend/app.py
- Flask application with comprehensive error handling - Database: MySQL 5.6+ compatible with utf8 charset (not utf8mb4)
- Connection String:
mysql://root:root@localhost/article_system?charset=utf8&use_unicode=1
- API Endpoints:
/api/articles/*
- Full CRUD operations for admin/api/mobile/articles/*
- Read-only API for mobile display (published articles only)/api/upload/image
- Image upload with Pillow compression and processing
- Models:
Article
: id, title, content, author, summary, cover_image, status, timestampsArticleTag
: id, article_id, tag_name
- Features: CORS enabled, image compression, automatic table creation
Frontend Architecture
- Framework: Vue 2.6.14 with Element UI 2.15.13
- Editor: TinyMCE 4.7.5 (CDN-loaded, not npm package)
- Key Components:
TinyMCEEditor.vue
: Main editor with 135 templates integrationArticleList.vue
: CRUD operations with data tableArticleEditor.vue
: Article creation/editing form
- API Layer:
src/api/articles.js
with axios - Proxy: Vue dev server proxies
/api
tolocalhost:5000
Mobile Display Architecture
- Location:
mobile/
directory - Vanilla JavaScript SPA - Pages:
index.html
: Article list with infinite scrollarticle.html?id=N
: Dedicated article detail page (not modal)
- Features:
- Responsive CSS Grid layout
- Image click-to-zoom modal
- Auto-detects host IP for LAN access
- Optimized for 135 Editor template display
- API: Consumes mobile API endpoints for published articles only
Key Technical Considerations
TinyMCE 4.x Integration
- Uses legacy
editor.addButton()
API (noteditor.ui.registry
) onclick
event handlers (notonAction
)- Content loading requires polling for editor initialization
- Method:
waitForEditorAndSetContent()
in ArticleEditor.vue
Database Compatibility
- MySQL 5.6 compatible using utf8 charset
- Avoids utf8mb4 for broader compatibility
- Manual table creation via
init.sql
- Character encoding issues handled by batch scripts
Image Handling
- Upload endpoint:
/api/upload/image
- Automatic compression with Pillow
- Thumbnails generated (1200x800 max)
- JPEG conversion for consistency
- Stored in
backend/uploads/
directory
Windows Environment Support
- Comprehensive batch file automation
- Handles Node.js and Python service management
- Firewall configuration for LAN access
- Chinese character encoding issues resolved
- Multiple fallback methods for database initialization
Adding 135 Editor Templates
Templates are defined in TinyMCEEditor.vue
component data:
templates: {
categoryName: [
{
id: 'unique_id',
name: 'Template Name',
description: 'Template description',
tags: ['tag1', 'tag2', 'tag3'],
html: '<section style="...">HTML content with inline styles</section>'
}
]
}
Template Guidelines:
- Use inline styles (not CSS classes)
- Wrap in
<section>
tags - Include responsive design considerations
- Add descriptive tags for search functionality
- Test on both desktop and mobile displays