предисловие
Вы, должно быть, использовали инструменты для создания микросцен, такие как Yiqixiu или Baidu H5, для создания крутых страниц h5.Помимо того, что вы вздыхаете о его магии, вы когда-нибудь задумывались о его реализации? В этой статье реализована полная идея дизайна и основные этапы реализации проекта редактора H5 с нуля, а исходный код внешнего и внутреннего интерфейса открыт. Небольшие партнеры, нуждающиеся в помощи, могут воспользоваться этим учебным пособием, чтобы создать собственный редактор H5 с нуля. (Это не сложно реализовать, в этом руководстве представлены только идеи, а не лучшие практики)
Github: портал
Демонстрационный адрес:портал
Предварительный просмотр редактора:
стек технологий
внешний интерфейс:
vue
: Модульная разработка незаменима для angular, react и vue, и здесь выбран vue.
vuex
: государственное управление
sass
: прекомпилятор css.
element-ui
: Не стройте колеса, конечно, есть готовые отличные библиотеки компонентов vue. Если у вас его нет, вы можете переупаковать его самостоятельно.
loadsh
:Инструменты
Сервер:
koa
: Nodejs используется в качестве внутреннего языка, и есть много koa документов и учебных материалов.Он был создан оригинальной командой экспресс, что в самый раз.
mongodb
: база данных на основе распределенного хранилища файлов, которая является более гибкой.
подготовиться перед чтением
1. Понимание разработки стека технологий vue
2. Понять коа
3. Понимание монгодб
Инженерное сооружение
Сборка на основе среды vue-cli3
- Как спланировать структуру каталогов нашего проекта? Сначала нам нужно иметь каталог в качестве внешнего проекта и каталог в качестве внутреннего проекта. Итак, нам нужно изменить структуру проекта, сгенерированную vue-cli:
···
·
|-- client // 原 src 目录,改成 client 用作前端项目目录
|-- server // 新增 server 用于服务端项目目录
|-- engine-template // 新增 engine-template 用于页面模板库目录
|-- docs // 新增 docs 预留编写项目文档目录
·
···
-
В этом случае нам нужно внести некоторые коррективы в наш конфигурационный файл webpack.Во-первых, изменить каталог, куда указывает исходная компиляция, на src to client.Во-вторых, чтобы npm run build нормально скомпилировал клиент, нам также нужно добавить другой каталог компиляции для babel-loader. :
-
Добавьте vue.config.js в корневой каталог, цель состоит в том, чтобы преобразовать запись проекта, изменить на: client/main.js
module.exports = { pages: { index: { entry: "client/main.js" } } }
-
babel-loader может нормально компилировать каталоги client и engine-template и добавлять следующую конфигурацию в vue.config.js
// 扩展 webpack 配置 chainWebpack: config => { config.module .rule('js') .include.add(/engine-template/).end() .include.add(/client/).end() .use('babel') .loader('babel-loader') .tap(options => { // 修改它的选项... return options }) }
-
Таким образом, мы создали простую структуру каталогов проекта.
Структура каталогов проекта
|-- client --------前端项目界面代码
|--common --------前端界面对应静态资源
|--components --------组件
|--config --------配置文件
|--eventBus --------eventBus
|--filter --------过滤器
|--mixins --------混入
|--pages --------页面
|--router --------路由配置
|--store --------vuex状态管理
|--service --------axios封装
|--App.vue --------App
|--main.js --------入口文件
|--permission.js --------权限控制
|-- server --------服务器端项目代码
|--confog --------数据库链接相关
|--middleware --------中间件
|--models --------Schema和Model
|--routes --------路由
|--views --------ejs页面模板
|--public --------静态资源
|--utils --------工具方法
|--app.js --------服务端入口
|-- common --------前后端公用代码模块(如加解密)
|-- engine-template --------页面模板引擎,使用webpack打包成js提供页面引用
|-- docs --------预留编写项目文档目录
|-- config.json --------配置文件
Реализация внешнего редактора
Идея реализации редактора такова: редактор генерирует данные JSON страницы, сервер отвечает за доступ к данным JSON, а при рендеринге данные JSON берутся с сервера и отправляются в шаблон фронтенда для обработки.
структура данных
После подтверждения логики реализации структура данных также очень важна.Чтобы определить страницу как данные JSON, структура данных будет примерно такой:
Интерфейс инженерных данных страницы
{
title: '', // 标题
description: '', //描述
coverImage: '', // 封面
auther: '', // 作者
script: '', // 页面插入脚本
width: 375, // 高
height: 644, // 宽
pages: [], // 多页页面
shareConfig: {}, // 微信分享配置
pageMode: 0, // 渲染模式,用于扩展多种模式渲染,翻页h5/长页/PC页面等等
}
Одностраничная структура данных многостраничных страниц:
{
name: '',
elements: [], // 页面元素
commonStyle: {
backgroundColor: '',
backgroundImage: '',
backgroundSize: 'cover'
},
config: {}
}
Структура данных элемента:
{
elName: '', // 组件名
animations: [], // 图层的动画,可以支持多个动画
commonStyle: {}, // 公共样式,默认样式
events: [], // 事件配置数据,每个图层可以添加多个事件
propsValue: {}, // 属性参数
value: '', // 绑定值
valueType: 'String', // 值类型
isForm: false // 是否是表单控件,用于表单提交时获取表单数据
}
Общий дизайн редактора
- Область выбора компонентов, предоставляющая пользователям возможность выбора необходимых компонентов.
- Монтажная область предварительного просмотра редактирования, которая предоставляет пользователям функцию перетаскивания и сортировки предварительных просмотров страниц.
- Редактор свойств компонента, который предоставляет пользователям возможность редактировать внутренние свойства компонента, общедоступные стили и анимацию.
Как показано на рисунке:
Пользователь выбирает компонент в области компонентов слева, чтобы добавить его на страницу, а область редактирования отображает каждый компонент элемента с помощью функции динамического компонента.
最后,点击保存将页面数据提交到数据库。至于数据怎么转成静态 HTML方法有很多。还有页面数据我们全部都有,我们可以做页面的预渲染,骨架屏,ssr,编译时优化等等。而且我们也可以对产出的活动页做数据分析~有很多想象的空间。
основной код
Основной код редактора реализован на основе функции динамического компонента Vue:
Прикрепите официальную документацию Vue для всех:cn.vuejs.org/v2/api/#is
Рендеринг элементов артборда
Чтобы отредактировать монтажную область, вам нужно всего лишь пройтись по массиву pages[i].elements, извлечь из него данные JSON компонентов элементов, визуализировать каждый компонент через динамические компоненты и поддерживать перетаскивание для изменения положения и размера. .
Управление компонентами элементов
Создайте плагины в каталоге клиента для управления библиотекой компонентов. Вы также можете отправить библиотеку компонентов в проект на npm и управлять ею через npm
Библиотека компонентов
При написании компонентов мы учитываем библиотеку компонентов, поэтому мы можем сделать так, чтобы наши компоненты поддерживали глобальный импорт и импорт по требованию.Если глобальный импорт, то все компоненты должны быть зарегистрированы в компоненте Vue и экспортированы:
Создайте новый файл записи index.js в разделе client/plugins.
```
/**
* 组件库入口
* */
import Text from './text'
// 所有组件列表
const components = [
Text
]
// 定义 install 方法,接收 Vue 作为参数
const install = function (Vue) {
// 判断是否安装,安装过就不继续往下执行
if (install.installed) return
install.installed = true
// 遍历注册所有组件
components.map(component => Vue.component(component.name, component))
}
// 检测到 Vue 才执行,毕竟我们是基于 Vue 的
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
install,
// 所有组件,必须具有 install,才能使用 Vue.use()
Text
}
```
разработка компонента
Пример: текстовый компонент текста
Создайте новый каталог текстовых компонентов в разделе client/plugins.
|-- text --------text组件
|--src --------资源
|--index.vue --------组件
|--index.js --------入口
text/index.js
// 为组件提供 install 方法,供组件对外按需引入
import Component from './src/index'
Component.install = Vue => {
Vue.component(Component.name, Component)
}
export default Component
text/src/index.vue
<!--text.vue-->
<template>
<div class="qk-text">
{{text}}
</div>
</template>
<script>
export default {
name: 'QkText', // 这个名字很重要,它就是未来的标签名<qk-text></qk-text>
props: {
text: {
type: String,
default: '这是一段文字'
}
}
}
</script>
<style lang="scss" scoped>
</style>
Редактор с использованием библиотеки компонентов:
// 引入组件库
import QKUI from 'client/plugins/index'
// 注册组件库
Vue.use(QKUI)
// 使用:
<qk-text text="这是一段文字"></qk-text>
В соответствии с этим методом разработки компонентов мы можем расширить любое количество компонентов, чтобы обогатить библиотеку компонентов.
需要注意的是这里的组件最外层宽高都要求是100%
конфигурационный файл
Выберите область компонентов в левой части редактора Quark-h5, чтобы определить дополнительные компоненты через файл конфигурации. Создайте новый файл конфигурации ele-config.js:
export default [
{
title: '基础组件',
components: [
{
elName: 'qk-text', // 组件名,与组件库名称一致
title: '文字',
icon: 'iconfont iconwenben',
// 给每个组件配置默认显示样式
defaultStyle: {
height: 40
}
}
]
},
{
title: '表单组件',
components: []
},
{
title: '功能组件',
components: []
},
{
title: '业务组件',
components: []
}
]
В общедоступном методе предоставляется функция для получения JSON компонента элемента по имени компонента и стилю по умолчанию, метод getElementConfigJson(elName, defaultStyle)
Редактирование атрибутов элемента
редактирование стиля общедоступной собственности
Редактировать свойства общего стиля относительно просто: нужно отредактировать поле commonStyles объекта JSON элемента.
Редактирование свойств реквизита
1. Разработайте компонент редактирования свойств для каждого свойства свойства компонента.Например: компоненту QkText требуется текстовое свойство, добавьте компонент attr-qk-text для работы с этим свойством. 2. Получите объект реквизита компонента 3. Перейдите по ключу объекта реквизита и оцените, какие компоненты редактирования свойств отображаются с помощью ключа.
Реализация анимации добавления элементов
Анимационные эффекты представлены в анимационной библиотеке Animate.css. Анимация компонента элемента может поддерживать несколько анимаций. Данные хранятся в массиве анимаций объекта JSON элемента.
Наведите курсор на панель, чтобы просмотреть анимацию.
Мониторинг мыши и MouseLeave, добавьте элемент анимации в элемент, когда мышь переместится, и удалить анимацию LASSNAME, когда мышь переместится. Это реализует анимацию предварительного просмотра.
Редактировать предварительный просмотр анимации
Редактирование компонентов поддерживает предварительный просмотр анимации и предварительный просмотр одиночной анимации.Инкапсулировать метод выполнения анимации
/**
* 动画方法, 将动画css加入到元素上,返回promise提供执行后续操作(将动画重置)
* @param $el 当前被执行动画的元素
* @param animationList 动画列表
* @param isDebugger 动画列表
* @returns {Promise<void>}
*/
export default async function runAnimation($el, animationList = [], isDebug , callback){
let playFn = function (animation) {
return new Promise(resolve => {
$el.style.animationName = animation.type
$el.style.animationDuration = `${animation.duration}s`
// 如果是循环播放就将循环次数置为1,这样有效避免编辑时因为预览循环播放组件播放动画无法触发animationend来暂停组件动画
$el.style.animationIterationCount = animation.infinite ? (isDebug ? 1 : 'infinite') : animation.interationCount
$el.style.animationDelay = `${animation.delay}s`
$el.style.animationFillMode = 'both'
let resolveFn = function(){
$el.removeEventListener('animationend', resolveFn, false);
$el.addEventListener('animationcancel', resolveFn, false);
resolve()
}
$el.addEventListener('animationend', resolveFn, false)
$el.addEventListener('animationcancel', resolveFn, false);
})
}
for(let i = 0, len = animationList.length; i < len; i++){
await playFn(animationList[i])
}
if(callback){
callback()
}
}
animationIterationCount 如果是编辑模式的化动画只执行一次,不然无法监听到动画结束animationend事件
Кэшировать стиль стиля элемента перед выполнением анимации, а затем назначать исходный стиль элементу после выполнения анимации.
let cssText = this.$el.style.cssText;
runAnimations(this.$el, animations, true, () => {
this.$el.style.cssText = cssText
})
событие добавления элемента
Предоставьте примеси событий в компонент, каждый метод события возвращает обещание, и метод события выполняется в порядке, когда элемент щелкнут.
страница вставить скрипт js
Ссылаясь на Baidu H5, вставьте скрипт в виде тега скрипта. Выполняется после загрузки страницы. Здесь также можно рассматривать примеси для смешивания со страницами или компонентами, которые могут быть расширены в соответствии с потребностями бизнеса, и все это может быть достигнуто.
повтор/отмена исторической записи операции
- Исторические записи операций хранятся в массиве store.state.editor.historyCache конечного автомата.
- Перемещайте все поле pageDataJson в historyCache каждый раз, когда изменяется операция редактирования.
- При нажатии кнопки повтор/отмена pageDataJson получается в соответствии с индексом для повторного рендеринга страницы.
Импорт чертежа PSD для создания страницы h5
Экспортируйте каждый слой в каждом чертеже PSD в виде изображения и сохраните его на сервере статических ресурсов,
Установка сервера опирается на PSD
cnpm install psd --save
Добавьте зависимости psd.js и предоставьте интерфейсы для обработки данных.
var PSD = require('psd');
router.post('/psdPpload',async ctx=>{
const file = ctx.request.files.file; // 获取上传文件
let psd = await PSD.open(file.path)
var timeStr = + new Date();
let descendantsList = psd.tree().descendants();
descendantsList.reverse();
let psdSourceList = []
let currentPathDir = `public/upload_static/psd_image/${timeStr}`
for (var i = 0; i < descendantsList.length; i++){
if (descendantsList[i].isGroup()) continue;
if (!descendantsList[i].visible) continue;
try{
await descendantsList[i].saveAsPng(path.join(ctx.state.SERVER_PATH, currentPathDir + `/${i}.png`))
psdSourceList.push({
...descendantsList[i].export(),
type: 'picture',
imageSrc: ctx.state.BASE_URL + `/upload_static/psd_image/${timeStr}/${i}.png`,
})
}catch (e) {
// 转换不出来的图层先忽略
continue;
}
}
ctx.body = {
elements: psdSourceList,
document: psd.tree().export().document
};
})
Наконец, извлеките полученные данные и верните их во внешний интерфейс.После того, как передний конец получит данные, он использует унифицированный метод системы для обхода и добавления компонента унифицированного изображения.
- Размер исходного файла psd не должен превышать 30М, если он будет слишком большим, браузер зависнет или даже зависнет.
- Объединяйте слои, где это возможно, и растрируйте все слои.
- Более сложные стили слоя, такие как фильтры, стили слоя и т. д., не могут быть прочитаны.
html2canvas генерирует миниатюры
Здесь вам нужно только обратить внимание на перекрестную проблему снимков. Официальный HTML2CANVAS: прокси-решение предоставляется. Он преобразует изображение в формат Base64 и использует настройку (прокси: TheProxyurl) вместе. При нанесении перекрестного доменного изображения он будет получать доступ к преобразованному изображению под The TheProxyurl, таким образом решение проблемы загрязнения холста. Укажите перекрестный интерфейс
/**
* html2canvas 跨域接口设置
*/
router.get('/html2canvas/corsproxy', async ctx => {
ctx.body = await request(ctx.query.url)
})
шаблон рендеринга
реализовать логику
Создайте новый компонент страницы swiper-h5-engine в каталоге engine-template.Этот компонент может отображать страницу после получения данных JSON страницы. Это похоже на логику реализации редактирования монтажной области предварительного просмотра.
Затем используйте команду упаковки библиотеки vue-cli, чтобы упаковать компонент в файл библиотеки engine.js. Шаблон ejs вводит компонент страницы и отображает страницу с данными json.
Схема адаптации
Предоставьте два решения для решения экранной адаптации 1. Равное масштабирование При преобразовании элемента json в элемент dom преобразуется отношение всех единиц px, а формула преобразования выглядит следующим образом: new = old * windows.x / pageJson.width, где pageJson.width — начальное значение страницы, т. е. также время редактирования Ширина по умолчанию, в то время как область просмотра использует ширину устройства. 2. Полноэкранный фон, страница центрирована по вертикали Поскольку будут пробелы вверх и вниз или влево и вправо, в это время мы обрабатываем цвет фона в полноэкранном режиме.
页面垂直居中只适用于全屏h5, 以后扩展长页和PC页就不需要垂直居中处理。
Пакет шаблонов
Новая команда упаковки в package.json
"lib:h5-swiper": "vue-cli-service build --target lib --name h5-swiper --dest server/public/engine_libs/h5-swiper engine-template/engine-h5-swiper/index.js"
Выполните npm run lib:h5-swiper, чтобы сгенерировать js-шаблон движка, как показано на рисунке.
рендеринг страницы
Внедрение шаблонов в ejs
<script src="/third-libs/swiper.min.js"></script>
использовать компоненты
<engine-h5-swiper :pageData="pageData" />
серверная служба
Инициализировать проект
Каталог проекта был указан выше, и его также можно сгенерировать с помощью инструмента скаффолдинга koa-generator.
конфигурация механизма шаблонов ejs-template
app.js
//配置ejs-template 模板引擎
render(app, {
root: path.join(__dirname, 'views'),
layout: false,
viewExt: 'html',
cache: false,
debug: false
});
koa-static служба статических ресурсов
Поскольку html2canvas требует, чтобы изображения разрешали междоменное использование, для всех запросов ресурсов в службе статических ресурсов устанавливается значение «Access-Control-Allow-Origin»: «*»
app.js
//配置静态web
app.use(koaStatic(__dirname + '/public'), { gzip: true, setHeaders: function(res){
res.header( 'Access-Control-Allow-Origin', '*')
}});
Измените метод регистрации маршрута и прочитайте файл, пройдя через папку маршрутов.
app.js
const fs = require('fs')
fs.readdirSync(path.join(__dirname,'./routes')).forEach(route=> {
let api = require(`./routes/${route}`)
app.use(api.routes(), api.allowedMethods())
})
Добавьте аутентификацию jwt и отфильтруйте маршруты, не требующие аутентификации, например получение токена.
app.js
const jwt = require('koa-jwt')
app.use(jwt({ secret: 'yourstr' }).unless({
path: [
/^\/$/, /\/token/, /\/wechat/,
{ url: /\/papers/, methods: ['GET'] }
]
}));
Промежуточное ПО реализует унифицированный интерфейс для возврата формата данных, фиксирует глобальные ошибки и реагирует на них.
middleware/formatresponse.js
module.exports = async (ctx, next) => {
await next().then(() => {
if (ctx.status === 200) {
ctx.body = {
message: '成功',
code: 200,
body: ctx.body,
status: true
}
} else if (ctx.status === 201) { // 201处理模板引擎渲染
} else {
ctx.body = {
message: ctx.body || '接口异常,请重试',
code: ctx.status,
body: '接口请求失败',
status: false
}
}
}).catch((err) => {
if (err.status === 401) {
ctx.status = 401;
ctx.body = {
code: 401,
status: false,
message: '登录过期,请重新登录'
}
} else {
throw err
}
})
}
koa2-cors междоменная обработка
Когда интерфейс публикуется в сети, а внешний интерфейс запрашивает через ajax, будет сообщено о междоменной ошибке. koa2 использует библиотеку koa2-cors для простой реализации междоменной конфигурации, и она очень проста в использовании.
const cors = require('koa2-cors');
app.use(cors());
Подключиться к базе данных
Мы используем базу данных mongodb и используем библиотеку mongoose в koa2 для управления работой всей базы данных.
- Создать файл конфигурации
Новая папка конфигурации в корневом каталоге New Mongo.JS
// config/mongo.js
const mongoose = require('mongoose').set('debug', true);
const options = {
autoReconnect: true
}
// username 数据库用户名
// password 数据库密码
// localhost 数据库ip
// dbname 数据库名称
const url = 'mongodb://username:password@localhost:27017/dbname'
module.exports = {
connect: ()=> {
mongoose.connect(url,options)
let db = mongoose.connection
db.on('error', console.error.bind(console, '连接错误:'));
db.once('open', ()=> {
console.log('mongodb connect suucess');
})
}
}
Поместите информацию о конфигурации mongodb в config.json для унифицированного управления.
- Затем импортируйте его в app.js
const mongoConf = require('./config/mongo');
mongoConf.connect();
... Реализация конкретного интерфейса сервера подробно описываться не будет, то есть добавление, удаление, модификация и проверка страницы, вход и регистрация пользователя не представляют сложности.
начать бежать
Запустите интерфейс
npm run dev-client
Запустить сервер
npm run dev-server
Уведомление: 如果没有生成过引擎模板js文件的,需要先编辑引擎模板,否则预览页面加载页面引擎.js 404报错
Компиляция шаблона Engine Engine.js
npm run lib:h5-swiper
пожертвовать автору
Если вы чувствуете, что этот проект полезен или вдохновляет вас, вы можете попросить чашку сока :( Тонг)