九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Vue Koa從零打造一個H5頁面可視化編輯器——Quark-h5

前言

想必你一定使用過易企秀或百度H5等微場景生成工具制作過炫酷的h5頁面,除了感嘆其神奇之處有沒有想過其實現(xiàn)方式呢?本文從零開始實現(xiàn)一個H5編輯器項目完整設(shè)計思路和主要實現(xiàn)步驟,并開源前后端代碼。有需要的小伙伴可以按照該教程從零實現(xiàn)自己的H5編輯器。(實現(xiàn)起來并不復(fù)雜,該教程只是提供思路,并非最佳實踐)

Github: https://github.com/huangwei9527/quark-h5

演示地址:http://47.104.247.183:4000/

演示賬號密碼均admin

編輯器預(yù)覽:

技術(shù)棧

前端: vue: 模塊化開發(fā)少不了angular,react,vue三選一,這里選擇了vue。 vuex: 狀態(tài)管理 sass: css預(yù)編譯器。 element-ui:不造輪子,有現(xiàn)成的優(yōu)秀的vue組件庫當(dāng)然要用起來。沒有的自己再封裝一些就可以了。 loadsh:工具類

服務(wù)端: koa:后端語言采用nodejs,koa文檔和學(xué)習(xí)資料也比較多,express原班人馬打造,這個正合適。 mongodb:一個基于分布式文件存儲的數(shù)據(jù)庫,比較靈活。

閱讀前準(zhǔn)備

1、了解vue技術(shù)棧開發(fā) 2、了解koa 3、了解mongodb

工程搭建

基于vue-cli3環(huán)境搭建

  • 如何規(guī)劃好我們項目的目錄結(jié)構(gòu)?首先我們需要有一個目錄作為前端項目,一個目錄作為后端項目。所以我們要對vue-cli 生成的項目結(jié)構(gòu)做一下改造:
··· · |-- client // 原 src 目錄,改成 client 用作前端項目目錄 |-- server // 新增 server 用于服務(wù)端項目目錄 |-- engine-template // 新增 engine-template 用于頁面模板庫目錄 |-- docs // 新增 docs 預(yù)留編寫項目文檔目錄 · ··· 復(fù)制代碼
  • 這樣的話 我們需要再把我們webpack配置文件稍作一下調(diào)整,首先是把原先的編譯指向src的目錄改成client,其次為了 npm run build 能正常編譯 client 我們也需要為 babel-loader 再增加一個編譯目錄: 根目錄新增vue.config.js,目的是為了改造項目入口,改為:client/main.js module.exports = { pages: { index: { entry: 'client/main.js' } } } 復(fù)制代碼 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 }) } 復(fù)制代碼

這樣我們搭建起來一個簡易的項目目錄結(jié)構(gòu)。

工程目錄結(jié)構(gòu)

|-- client					--------前端項目界面代碼
    |--common					--------前端界面對應(yīng)靜態(tài)資源
    |--components				--------組件
    |--config					--------配置文件
    |--eventBus					--------eventBus
    |--filter					--------過濾器
    |--mixins					--------混入
    |--pages					--------頁面
    |--router					--------路由配置
    |--store					--------vuex狀態(tài)管理
    |--service					--------axios封裝
    |--App.vue					--------App
    |--main.js					--------入口文件
    |--permission.js			--------權(quán)限控制
|-- server					--------服務(wù)器端項目代碼
    |--confog					--------數(shù)據(jù)庫鏈接相關(guān)
    |--middleware				--------中間件
    |--models					--------Schema和Model
    |--routes					--------路由
    |--views					--------ejs頁面模板
    |--public					--------靜態(tài)資源
    |--utils					--------工具方法
    |--app.js					--------服務(wù)端入口
|-- common					--------前后端公用代碼模塊(如加解密)
|-- engine-template			--------頁面模板引擎,使用webpack打包成js提供頁面引用
|-- docs					--------預(yù)留編寫項目文檔目錄
|-- config.json				--------配置文件
復(fù)制代碼

前端編輯器實現(xiàn)

編輯器的實現(xiàn)思路是:編輯器生成頁面JSON數(shù)據(jù),服務(wù)端負責(zé)存取JSON數(shù)據(jù),渲染時從服務(wù)端取數(shù)據(jù)JSON交給前端模板處理。

數(shù)據(jù)結(jié)構(gòu)

確認了實現(xiàn)邏輯,數(shù)據(jù)結(jié)構(gòu)也是非常重要的,把一個頁面定義成一個JSON數(shù)據(jù),數(shù)據(jù)結(jié)構(gòu)大致是這樣的:

頁面工程數(shù)據(jù)接口

{ title: '', // 標(biāo)題 description: '', //描述 coverImage: '', // 封面 auther: '', // 作者 script: '', // 頁面插入腳本 width: 375, // 高 height: 644, // 寬 pages: [], // 多頁頁面 shareConfig: {}, // 微信分享配置 pageMode: 0, // 渲染模式,用于擴展多種模式渲染,翻頁h5/長頁/PC頁面等等 } 復(fù)制代碼

多頁頁面pages其中一頁數(shù)據(jù)結(jié)構(gòu):

{
	name: '',
	elements: [], // 頁面元素
	commonStyle: {
		backgroundColor: '',
		backgroundImage: '',
		backgroundSize: 'cover'
	},
	config: {}
}
復(fù)制代碼

元素數(shù)據(jù)結(jié)構(gòu):

{ elName: '', // 組件名 animations: [], // 圖層的動畫,可以支持多個動畫 commonStyle: {}, // 公共樣式,默認樣式 events: [], // 事件配置數(shù)據(jù),每個圖層可以添加多個事件 propsValue: {}, // 屬性參數(shù) value: '', // 綁定值 valueType: 'String', // 值類型 isForm: false // 是否是表單控件,用于表單提交時獲取表單數(shù)據(jù) } 復(fù)制代碼

編輯器整體設(shè)計

  • 一個組件選擇區(qū),提供使用者選擇需要的組件
  • 一個編輯預(yù)覽畫板,提供使用者拖拽排序頁面預(yù)覽的功能
  • 一個組件屬性編輯,提供給使用者編輯組件內(nèi)部props、公共樣式和動畫的功能 如圖:

用戶在左側(cè)組件區(qū)域選擇組件添加到頁面上,編輯區(qū)域通過動態(tài)組件特性渲染出每個元素組件。

最后,點擊保存將頁面數(shù)據(jù)提交到數(shù)據(jù)庫。至于數(shù)據(jù)怎么轉(zhuǎn)成靜態(tài) HTML方法有很多。還有頁面數(shù)據(jù)我們?nèi)慷加?,我們可以做頁面的預(yù)渲染,骨架屏,ssr,編譯時優(yōu)化等等。而且我們也可以對產(chǎn)出的活動頁做數(shù)據(jù)分析~有很多想象的空間。

核心代碼

編輯器核心代碼,基于 Vue 動態(tài)組件特性實現(xiàn):

為大家附上 Vue 官方文檔:cn.vuejs.org/v2/api/#is

畫板元素渲染

編輯畫板只需要循環(huán)遍歷pages[i].elements數(shù)組,將里面的元素組件JSON數(shù)據(jù)取出,通過動態(tài)組件渲染出各個組件,支持拖拽改變位置尺寸.

元素組件管理

在client目錄新建plugins來管理組件庫。也可以將該組件庫發(fā)到npm上工程中通過npm管理

組件庫

編寫組件,考慮的是組件庫,所以我們竟可能讓我們的組件支持全局引入和按需引入,如果全局引入,那么所有的組件需要要注冊到Vue component 上,并導(dǎo)出:

client/plugins下新建index.js入口文件

```
/**
 * 組件庫入口
 * */
import Text from './text'
// 所有組件列表
const components = [
	Text
]
// 定義 install 方法,接收 Vue 作為參數(shù)
const install = function (Vue) {
	// 判斷是否安裝,安裝過就不繼續(xù)往下執(zhí)行
	if (install.installed) return
	install.installed = true
	// 遍歷注冊所有組件
	components.map(component => Vue.component(component.name, component))
}

// 檢測到 Vue 才執(zhí)行,畢竟我們是基于 Vue 的
if (typeof window !== 'undefined' && window.Vue) {
	install(window.Vue)
}

export default {
	install,
	// 所有組件,必須具有 install,才能使用 Vue.use()
	Text
}
```
復(fù)制代碼

組件開發(fā)

示例: text文本組件

client/plugins下新建text組件目錄

|-- text --------text組件 |--src --------資源 |--index.vue --------組件 |--index.js --------入口 復(fù)制代碼

text/index.js

// 為組件提供 install 方法,供組件對外按需引入
import Component from './src/index'
Component.install = Vue => {
	Vue.component(Component.name, Component)
}
export default Component
復(fù)制代碼

text/src/index.vue

<!--text.vue--> <template> <div class='qk-text'> {{text}} </div> </template> <script> export default { name: 'QkText', // 這個名字很重要,它就是未來的標(biāo)簽名<qk-text></qk-text> props: { text: { type: String, default: '這是一段文字' } } } </script> <style lang='scss' scoped> </style> 復(fù)制代碼

編輯器里使用組件庫:

// 引入組件庫
import QKUI from 'client/plugins/index'
// 注冊組件庫
Vue.use(QKUI)

// 使用:
<qk-text text='這是一段文字'></qk-text>
復(fù)制代碼

按照這個組件開發(fā)方式我們可以擴展任意多的組件,來豐富組件庫

需要注意的是這里的組件最外層寬高都要求是100%

配置文件

Quark-h5編輯器左側(cè)選擇組件區(qū)域可以通過一個配置文件定義可選組件 新建一個ele-config.js配置文件:

export default [ { title: '基礎(chǔ)組件', components: [ { elName: 'qk-text', // 組件名,與組件庫名稱一致 title: '文字', icon: 'iconfont iconwenben', // 給每個組件配置默認顯示樣式 defaultStyle: { height: 40 } } ] }, { title: '表單組件', components: [] }, { title: '功能組件', components: [] }, { title: '業(yè)務(wù)組件', components: [] } ] 復(fù)制代碼

公共方法中提供一個function 通過組件名和默認樣式獲取元素組件JSON,getElementConfigJson(elName, defaultStyle)方法

元素屬性編輯

公共屬性樣式編輯

公共樣式屬性編輯比較簡單就是對元素JSON對象commonStyles字段進行編輯操作

props屬性編輯

1.為組件的每一個prop屬性開發(fā)一個屬性編輯組件. 例如:QkText組件需要text屬性,新增一個attr-qk-text組件來操作該屬性 2.獲取組件prop對象 3.遍歷prop對象key, 通過key判斷顯示哪些屬性編輯組件

元素添加動畫實現(xiàn)

動畫效果引入Animate.css動畫庫。元素組件動畫,可以支持多個動畫。數(shù)據(jù)存在元素JSON對象animations數(shù)組里。

選擇面板hover預(yù)覽動畫

監(jiān)聽mouseover和mouseleave,當(dāng)鼠標(biāo)移入時將動畫className添加入到元素上,鼠標(biāo)移出時去掉動畫lassName。這樣就實現(xiàn)了hover預(yù)覽動畫

編輯預(yù)覽動畫

組件編輯時支持動畫預(yù)覽和單個動畫預(yù)覽。

封裝一個動畫執(zhí)行方法

/**
 * 動畫方法, 將動畫css加入到元素上,返回promise提供執(zhí)行后續(xù)操作(將動畫重置)
 * @param $el 當(dāng)前被執(zhí)行動畫的元素
 * @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`
			// 如果是循環(huán)播放就將循環(huán)次數(shù)置為1,這樣有效避免編輯時因為預(yù)覽循環(huán)播放組件播放動畫無法觸發(fā)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()
	}
}
復(fù)制代碼

animationIterationCount 如果是編輯模式的化動畫只執(zhí)行一次,不然無法監(jiān)聽到動畫結(jié)束animationend事件

執(zhí)行動畫前先將元素樣式style緩存起來,當(dāng)動畫執(zhí)行完再將原樣式賦值給元素

let cssText = this.$el.style.cssText; runAnimations(this.$el, animations, true, () => { this.$el.style.cssText = cssText }) 復(fù)制代碼

元素添加事件

提供事件mixins混入到組件,每個事件方法返回promise,元素被點擊時按順序執(zhí)行事件方法

頁面插入js腳本

參考百度H5,將腳本以script標(biāo)簽形式嵌入。頁面加載后執(zhí)行。 這里也可以考慮mixins方式混入到頁面或者組件,可根據(jù)業(yè)務(wù)需求自行擴展,都是可以實現(xiàn)的。

redo/undo歷史操作紀(jì)錄

  1. 歷史操作紀(jì)錄存在狀態(tài)機store.state.editor.historyCache數(shù)組中。
  2. 每次修改編輯操作都把整個pageDataJson字段push到historyCache
  3. 點擊redo/undo時根據(jù)index獲取到pageDataJson重新渲染頁面

psd設(shè)計圖導(dǎo)入生成h5頁面

將psd每個設(shè)計圖中的每個圖層導(dǎo)出成圖片保存到靜態(tài)資源服務(wù)器中,

服務(wù)端安裝psd依賴

cnpm install psd --save
復(fù)制代碼

加入psd.js依賴,并且提供接口來處理數(shù)據(jù)

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) { // 轉(zhuǎn)換不出來的圖層先忽略 continue; } } ctx.body = { elements: psdSourceList, document: psd.tree().export().document }; }) 復(fù)制代碼

最后把獲取的數(shù)據(jù)轉(zhuǎn)義并返回給前端,前端獲取到數(shù)據(jù)后使用系統(tǒng)統(tǒng)一方法,遍歷添加統(tǒng)一圖片組件

  • psd源文件大小最好不要超過30M,過大會導(dǎo)致瀏覽器卡頓甚至卡死
  • 盡可能合并圖層,并柵格化所有圖層
  • 較復(fù)雜的圖層樣式,如濾鏡、圖層樣式等無法讀取

html2canvas生成縮略圖

這里只需要注意下圖片跨域問題,官方提供html2canvas: proxy解決方案。它將圖片轉(zhuǎn)化為base64格式,結(jié)合使用設(shè)置(proxy: theProxyURL), 繪制到跨域圖片時,會去訪問theProxyURL下轉(zhuǎn)化好格式的圖片,由此解決了畫布污染問題。 提供一個跨域接口

/**
 * html2canvas 跨域接口設(shè)置
 */
router.get('/html2canvas/corsproxy', async ctx => {
	ctx.body =  await request(ctx.query.url)
})
復(fù)制代碼

渲染模板

實現(xiàn)邏輯

在engine-template目錄下新建swiper-h5-engine頁面組件,這個組件接收到頁面JSON數(shù)據(jù)就可以把頁面渲染出來。跟編輯預(yù)覽畫板實現(xiàn)邏輯差不多。

然后使用vue-cli庫打包命令將組件打包成engine.js庫文件。ejs模板引入該頁面組件配合json數(shù)據(jù)渲染出頁面

適配方案

提供兩種方案解決屏幕適配 1、等比例縮放 在將json元素轉(zhuǎn)換為dom元素的時候,對所有的px單位做比例轉(zhuǎn)換,轉(zhuǎn)換公式為 new = old * windows.x / pageJson.width,這里的pageJson.width是頁面的一個初始值,也是編輯時候的默認寬度,同時viewport使用device-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'

執(zhí)行npm run lib:h5-swiper 生成引擎模板js如圖

頁面渲染

ejs中引入模板

<script src='/third-libs/swiper.min.js'></script>

使用組件

<engine-h5-swiper :pageData='pageData' />

后端服務(wù)

初始化項目

工程目錄上文已給出,也可以使用 koa-generator 腳手架工具生成

ejs-template 模板引擎配置

app.js

//配置ejs-template 模板引擎 render(app, { root: path.join(__dirname, 'views'), layout: false, viewExt: 'html', cache: false, debug: false }); 復(fù)制代碼

koa-static靜態(tài)資源服務(wù)

因為html2canvas需要圖片允許跨域,所以在靜態(tài)資源服務(wù)中所有資源請求設(shè)置'Access-Control-Allow-Origin':'*'

app.js

//配置靜態(tài)web
app.use(koaStatic(__dirname   '/public'), { gzip: true, setHeaders: function(res){
	res.header( 'Access-Control-Allow-Origin', '*')
}});
復(fù)制代碼

修改路由的注冊方式,通過遍歷routes文件夾讀取文件

app.js

const fs = require('fs') fs.readdirSync('./routes').forEach(route=> { let api = require(`./routes/${route}`) app.use(api.routes(), api.allowedMethods()) }) 復(fù)制代碼

添加jwt認證,同時過濾不需要認證的路由,如獲取token

app.js

const jwt = require('koa-jwt')
app.use(jwt({ secret: 'yourstr' }).unless({
    path: [
        /^\/$/, /\/token/, /\/wechat/,
        { url: /\/papers/, methods: ['GET'] }
    ]
}));
復(fù)制代碼

中間件實現(xiàn)統(tǒng)一接口返回數(shù)據(jù)格式,全局錯誤捕獲并響應(yīng)

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 } }) } 復(fù)制代碼

koa2-cors跨域處理

當(dāng)接口發(fā)布到線上,前端通過ajax請求時,會報跨域的錯誤。koa2使用koa2-cors這個庫非常方便的實現(xiàn)了跨域配置,使用起來也很簡單

const cors = require('koa2-cors');
app.use(cors());
復(fù)制代碼

連接數(shù)據(jù)庫

我們使用mongodb數(shù)據(jù)庫,在koa2中使用mongoose這個庫來管理整個數(shù)據(jù)庫的操作。

  • 創(chuàng)建配置文件

根目錄下新建config文件夾,新建mongo.js

// config/mongo.js const mongoose = require('mongoose').set('debug', true); const options = { autoReconnect: true } // username 數(shù)據(jù)庫用戶名 // password 數(shù)據(jù)庫密碼 // localhost 數(shù)據(jù)庫ip // dbname 數(shù)據(jù)庫名稱 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'); }) } } 復(fù)制代碼

把mongodb配置信息放到config.json中統(tǒng)一管理

  • 然后在app.js中引入
const mongoConf = require('./config/mongo');
mongoConf.connect();
復(fù)制代碼

... 服務(wù)端具體接口實現(xiàn)就不詳細介紹了,就是對頁面的增刪改查,和用戶的登錄注冊難度不大

啟動運行

啟動前端

npm run dev-client 復(fù)制代碼

啟動服務(wù)端

npm run dev-server
復(fù)制代碼

注意: 如果沒有生成過引擎模板js文件的,需要先編輯引擎模板,否則預(yù)覽頁面加載頁面引擎.js 404報錯

編譯engine.js模板引擎

npm run lib:h5-swiper
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
2017年21 個編程的熱門/冷門趨勢
一個學(xué)習(xí) Koa 源碼的例子
嗶哩嗶哩 (B 站) 前端進階之路
VUE中快速添加水印的幾種方式
Vue項目啟動代碼執(zhí)行流程分析
【日記】145差點就斷更了,我擦
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服