2021-05-12 14:32:11
我打造了一個線上簡歷生成應用
我打造了一個線上簡歷生成應用
前言
半個月前,我寫了一篇文章如何書寫一份好的網際網路校招簡歷,目的是幫助即將開始投遞校招的同學更好的完善自己的簡歷
在文章中也立下了一個Flag
看了一下Github的 commit記錄,截止目前大概花了一週的時間,把心中所設想的第一版做了出來,也許不完美,但我想應該也能幫助到部分同學
好東西當然展示三遍,O(∩_∩)O~~
對模板樣式(顏色,排版)不滿意的,懂前端魔法的同學可以clone倉庫,施展一下自己的魔法美化
對專案感興趣的同學也歡迎貢獻一下自己喜歡的簡歷模板(程式碼),理論上不限制開發技術棧,當然也歡迎提issues或者建議
本文主要講一下此專案的設計思路,技術方案以及遇到的一些問題與解決思路(用了不少hack技巧)
專案設計
佈局
整個應用的基本頁面結構
<body>
<header>
<!-- 導航 -->
<nav></nav>
</header>
<div>
<!-- 展示簡歷 -->
<iframe></iframe>
<!-- 控制區域 -->
<div></div>
</div>
</body>
可能有朋友在這裡會疑惑為什麼要用iframe?
這裡先給大家簡單介紹一下,後面在講技術方案的時候會給大家解釋
在我的設想中簡歷部分只有展示邏輯,可以看作是一個獨立的純靜態頁面
既然是隻做展示,那麼無論什麼前端魔法都可以做這個工作,於是為了方便各種魔法師施法,就把這一塊獨立了出來,簡歷模板貢獻者也只需要關心自己如何復原一個靜態頁面就行,其餘的互動邏輯都交給父頁面統一處理
技術選型
Vanilla JS——世界上最輕量的JavaScript框架(沒有之一) ---- 原生js
整個應用的主體部分採用原生js實現
簡歷展示部分理論上可以採用任意前端技術棧實現,與父頁面低耦合
通訊
- 通過導航欄切換各種簡歷模板
- 簡歷上的改動自動同步到控制區域中的頁面描述資訊
- 控制區域中改動頁面描述資訊,簡歷內容實時更新
描述簡歷
- 使用json 對簡歷的結構與內容進行描述
- 一個模板對應一個json
頁面描述資訊展示
- 使用JSON描述簡歷上的各種資訊
- 提供一個JSON編輯器
- 這裡json編輯器採用 jsoneditor
資料存取
- 整個資料流是單向的,外部負責更新,內部(簡歷展示部分)只負責讀取
- 資料存放在本地,因此不擔心個人資訊洩露
- 這裡採用
localStorage
第一版效果
下面就介紹專案實現的關鍵部分內容
實現
專案目錄結構
./config webpack組態檔
├── webpack.base.js -- 公共設定
├── webpack.config.build.js -- 生產環境特有設定
├── webpack.config.dev.js -- 開發環境特有設定
├── webpack.config.js -- 參照的組態檔
│
./public 公共靜態資源
├── css
│ └── print.css 列印時用的樣式
│
./src 核心程式碼
├── assets 靜態資源css/img
├── constants 常數
│ ├── index.js 存放導航的名稱對映資訊
│ ├── schema 存放每個簡歷模板的預設JSON資料,與pages中的模板一一對應
│ └────── demo1.js
├── pages 簡歷模板目錄
│ └── demo1 -- 其中的一個模板
│
├── utils 工具方法
├── app.js 專案的入口js
├── index.html 專案的入口頁面
約定優於設定
根據約定好的目錄結構,通過自動化的指令碼
所有模板都統一在 src/pages/xxx 目錄下
頁面模板約定為 index.html
,該目錄下的所有js檔案將被自動新增到webpack的entry中,自動注入到 當前 頁面模板中
例如
./src
├── pages
│ └── xxx
│ └───── index.html
│ └───── index.scss
│ └───── index.js
此處自動化生成entry/page設定程式碼可移步至這裡檢視
自動生成的結果如下
每個HTMLWebpackPlugin的內容格式如下
自動生成導航欄
首頁頂部有一個導航欄用於切換簡歷模板的路由
這部分的連結內容如果手動填寫是很無趣的,如何實現自動生成的呢?
首先首頁模板的header nav 部分內容為
<header>
<nav id="nav">
<%= htmlWebpackPlugin.options.pageNames %>
</nav>
</header>
htmlWebpackPlugin.options
表示 HTMLWebpackPlugin
物件的的userOptions
屬性
咱們上面拿到了了所有Page的title,將所有title使用,
連線拼接在一起,然後繫結到userOptions.pageNames
上,則頁面初次渲染結果就變成了
<header>
<nav id="nav">
abc,demo1,vue1,react1,introduce
</nav>
</header>
有了初次渲染結果,接下來咱們寫一個方法把這些內容轉為a
標籤即可
const navTitle = {
'demo1': '模板1',
'react1': '模板2',
'vue1': '模板3',
'introduce': '使用檔案',
'abc': '開發範例'
}
function createLink(text, href, newTab = false) {
const a = document.createElement('a')
a.href = href
a.text = text
a.target = newTab ? '_blank' : 'page'
return a
}
/**
* 初始化導航欄
*/
function initNav(defaultPage = 'react1') {
const $nav = document.querySelector('header nav')
// 獲取所有模板的連結---處理原始內容
const links = $nav.innerText.split(',').map(pageName => {
const link = createLink(navTitle[pageName] || pageName, `./pages/${pageName}`)
// iframe中開啟
return link
})
// 加入自定義的連結
links.push(createLink('Github', 'https://github.com/ATQQ/resume', true))
links.push(createLink('貢獻模板', 'https://github.com/ATQQ/resume/blob/main/README.md', true))
links.push(createLink('如何書寫一份好的網際網路校招簡歷', 'https://juejin.cn/post/6928390537946857479', true))
links.push(createLink('建議/反饋', 'https://www.wenjuan.com/s/MBryA3gI/', true))
// 渲染到頁面中
const t = document.createDocumentFragment()
links.forEach(link => {
t.appendChild(link)
})
$nav.innerHTML = ''
$nav.append(t)
}
initNav()
這樣導航欄就「自動「生成了
自動匯出頁面描述
目錄
./src
├── constants
│ ├── index.js
│ ├── schema.js
│ ├── schema
│ ├────── demo1.js
│ ├────── react1.js
│ └────── vue1.js
每個頁面的預設資料從./src/constants/schema.js中讀取
import abc from './schema/abc'
import demo1 from './schema/demo1'
import react1 from './schema/react1'
import vue1 from './schema/vue1'
export default{
abc,demo1,react1,vue1
}
而每個模板的描述內容分佈在 schema目錄下,如果讓每個開發者手動往schema.js新增自己模板,容易造成衝突,所以乾脆自動生成
工具方法移步至這裡檢視
/**
* 自動建立src/constants/schema.js 檔案
*/
function writeSchemaJS() {
const files = getDirFilesWithFullPath('src/constants/schema')
const { dir } = path.parse(files[0])
const targetFilePath = path.resolve(dir, '../', 'schema.js')
const names = files.map(file => path.parse(file).name)
const res = `${names.map(n => {
return `import ${n} from './schema/${n}'`
}).join('n')}
export default{
${names.join(',')}
}`
fs.writeFileSync(targetFilePath, res)
}
資料存取
資料的存取操作在父頁面和子頁面都會用到,抽離為公共方法
資料存放於localStorage中,以每個簡歷模板的路由作為key
./src/utils/index.js
import defaultSchema from '../constants/schema'
export function getSchema(key = '') {
if (!key) {
// 預設key為路由 如 origin.com/pages/react1
// key就為 pages/react1
key = window.location.pathname.replace(//$/, '')
}
// 先從本地取
let data = localStorage.getItem(key)
// 如果沒有就設定一個預設的再取
if (!data) {
setSchema(getDefaultSchema(key), key)
return getSchema()
}
// 如果預設是空物件的則再取一次預設值
if (data === '{}') {
setSchema(getDefaultSchema(key), key)
data = localStorage.getItem(key)
}
return JSON.parse(data)
}
export function getDefaultSchema(key) {
const _key = key.slice(key.lastIndexOf('/') + 1)
return defaultSchema[_key] || {}
}
export function setSchema(data, key = '') {
if (!key) {
key = window.location.pathname.replace(//$/, '')
}
localStorage.setItem(key, JSON.stringify(data))
}
json描述的展示
需要在控制區域展示json的描述資訊,展示部分採用 jsoneditor
當然jsoneditor也支援各種資料操作(CRUD)都支援,還提供了快捷操作按鈕
這裡採用cdn的方式引入jsoneditor
<link rel="stylesheet" href="https://img.cdn.sugarat.top/css/jsoneditor.min.css">
<script src="https://img.cdn.sugarat.top/js/jsoneditor.min.js"></script>
初始化
/**
* 初始化JSON編輯器
* @param {string} id
*/
function initEditor(id) {
let timer = null
// 這裡做了一個簡單的防抖
const editor = new JSONEditor(document.getElementById(id), {
// json內容改動時觸發
onChangeJSON(data) {
if (timer) {
clearTimeout(timer)
}
// updatePage方法用於通知子頁面更新
setTimeout(updatePage, 200, data)
}
})
return editor
}
const editor = initEditor('jsonEditor')
展示效果
json資料展示/更新時機
- 因為每次切換路由都會觸發iframe的onload事件
- 所以將獲取editor更新json內容的時機放在這裡
function getPageKey() {
return document.getElementById('page').contentWindow.location.pathname.replace(//$/, '')
}
document.getElementById('page').onload = function (e) {
// 更新editor中顯示的內容
editor.set(getSchema(getPageKey()))
}
編寫模板頁面
下面提供了4種方式實現同一頁面
期望的效果
描述檔案
在schema目錄下建立頁面的json描述檔案,如abc.js
./src
├── constants
│ └── schema
│ └────── abc.js
abc.js
export default {
name: '王五',
position: '求職目標: Web前端工程師',
infos: [
'1:很多文字',
'2:很多文字',
'3:很多文字',
]
}
期望的渲染結構
<div id="resume">
<div id="app">
<header>
<h1>王五</h1>
<h2>求職目標: Web前端工程師</h2>
</header>
<ul class="infos">
<li>1:很多文字<li>
<li>2:很多文字<li>
<li>3:很多文字<li>
</ul>
</div>
</div>
下面開始子編寫程式碼
與父頁面唯一相關的邏輯就是需要在子頁面的window上掛載一個refresh方法,用於父頁面主動呼叫更新
原生js
import { getSchema } from "../../utils"
window.refresh = function () {
const schema = getSchema()
const { name, position, infos } = schema
// ... render邏輯
}
vue
<script>
import { getSchema } from '../../utils';
export default {
data() {
return {
schema: getSchema(),
};
},
mounted() {
window.refresh = this.refresh;
},
methods: {
refresh() {
this.schema = getSchema();
},
},
};
</script>
react
import React, { useEffect, useState } from 'react'
import { getSchema } from '../../utils'
export default function App() {
const [schema, updateSchema] = useState(getSchema())
const { name, position, infos = [] } = schema
useEffect(() => {
window.refresh = function () {
updateSchema(getSchema())
}
}, [])
return (
<div>
{ /* 渲染dom的邏輯 */ }
</div>
)
}
為方便閱讀,程式碼進行了摺疊
首先是樣式,這裡選擇sass預處理語言,當然也可以用原生css
index.scss
@import './../../assets/css/base.scss';
html,
body,
#resume {
height: 100%;
overflow: hidden;
}
// 上面部分是推薦引入的通用樣式
// 下面書寫我們的樣式
$themeColor: red;
#app {
padding: 1rem;
}
header {
h1 {
color: $themeColor;
}
h2 {
font-weight: lighter;
}
}
.infos {
list-style: none;
li {
color: $themeColor;
}
}
其次是頁面描述檔案
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<div id="resume">
<div id="app">
</div>
</div>
</body>
</html>
下面就開始使用各種技術棧進行邏輯程式碼編寫
原生js
目錄結構
./src
├── pages
│ └── abc
│ └───── index.html
│ └───── index.scss
│ └───── index.js
index.js
import { getSchema } from "../../utils"
import './index.scss'
window.refresh = function () {
const schema = getSchema()
const { name, position, infos } = schema
clearPage()
renderHeader(name, position)
renderInfos(infos)
}
function clearPage() {
document.getElementById('app').innerHTML = ''
}
function renderHeader(name, position) {
const html = `
<header>
<h1>${name}</h1>
<h2>${position}</h2>
</header>`
document.getElementById('app').innerHTML += html
}
function renderInfos(infos = []) {
if (infos?.length === 0) {
return
}
const html = `
<ul class="infos">
${infos.map(info => {
return `<li>${info}</li>`
}).join('')}
</ul>`
document.getElementById('app').innerHTML += html
}
window.onload = function () {
refresh()
}
Vue
目錄結構
./src
├── pages
│ └── abc
│ └───── index.html
│ └───── index.scss
│ └───── index.js
│ └───── App.vue
index.js
import Vue from 'vue'
import App from './App.vue'
import './index.scss'
Vue.config.productionTip = process.env.NODE_ENV === 'development'
new Vue({
render: h => h(App)
}).$mount('#app')
App.vue
<template>
<div id="app">
<header>
<h1>{{ schema.name }}</h1>
<h2>{{ schema.position }}</h2>
</header>
<div class="infos">
<p
v-for="(info,
i) in schema.infos"
:key="i"
>
{{ info }}
</p>
</div>
</div>
</template>
<script>
import { getSchema } from '../../utils';
export default {
data() {
return {
schema: getSchema(),
};
},
mounted() {
window.refresh = this.refresh;
},
methods: {
refresh() {
this.schema = getSchema();
},
},
};
</script>
React
目錄結構
./src
├── pages
│ └── abc
│ └───── index.html
│ └───── index.scss
│ └───── index.js
│ └───── App.jsx
index.js
import React from 'react'
import ReactDOM from 'react-dom';
import App from './App.jsx'
import './index.scss'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('app')
)
App.jsx
import React, { useEffect, useState } from 'react'
import { getSchema } from '../../utils'
export default function App() {
const [schema, updateSchema] = useState(getSchema())
const { name, position, infos = [] } = schema
useEffect(() => {
window.refresh = function () {
updateSchema(getSchema())
}
}, [])
return (
<div>
<header>
<h1>{name}</h1>
<h2>{position}</h2>
</header>
<div className="infos">
{
infos.map((info, i) => {
return <p key={i}>{info}</p>
})
}
</div>
</div>
)
}
jQuery
目錄結構
./src
├── pages
│ └── abc
│ └───── index.html
│ └───── index.scss
│ └───── index.js
index.js
import { getSchema } from "../../utils"
import './index.scss'
window.refresh = function () {
const schema = getSchema()
const { name, position, infos } = schema
clearPage()
renderHeader(name, position)
renderInfos(infos)
}
function clearPage() {
$('#app').empty()
}
function renderHeader(name, position) {
const html = `
<header>
<h1>${name}</h1>
<h2>${position}</h2>
</header>`
$('#app').append(html)
}
function renderInfos(infos = []) {
if (infos?.length === 0) {
return
}
const html = `
<ul class="infos">
${infos.map(info => {
return `<li>${info}</li>`
}).join('')}
</ul>`
$('#app').append(html)
}
window.onload = function () {
refresh()
}
如果覺得導航欄展示abc不友好,當然也可以更改
./src
├── constants
│ ├── index.js 存放路徑與中文title的對映
./src/constants/index.js 中加入別名
export const navTitle = {
'abc': '開發範例'
}
子頁面更新
前面在範例化editor的時候有一個 updatePage
方法
如果子頁面有refresh方法則直接 呼叫其進行頁面的更新,當然在更新之前父頁面會把最新的資料存入到localStorage中
這樣頁面之間實際沒有直接交換資料,一個負責寫,一個負責讀,即使寫入失敗也不影響子頁面讀取原有的資料
function refreshIframePage(isReload = false) {
const page = document.getElementById('page')
if (isReload) {
page.contentWindow.location.reload()
return
}
if (page.contentWindow.refresh) {
page.contentWindow.refresh()
return
}
page.contentWindow.location.reload()
}
function updatePage(data) {
setSchema(data, getPageKey())
refreshIframePage()
}
/**
* 初始化JSON編輯器
* @param {string} id
*/
function initEditor(id) {
let timer = null
// 這裡做了一個簡單的防抖
const editor = new JSONEditor(document.getElementById(id), {
// json內容改動時觸發
onChangeJSON(data) {
if (timer) {
clearTimeout(timer)
}
// updatePage方法用於通知子頁面更新
setTimeout(updatePage, 200, data)
}
})
return editor
}
const editor = initEditor('jsonEditor')
匯出pdf
PC端
首先PC端瀏覽器支援列印匯出pdf
如何觸發列印呢?
- 滑鼠右鍵選擇列印
- 快捷鍵 Ctrl + P
window.print()
咱們這裡程式碼裡使用第三種方案
如何確保列印的內容只有簡歷部分?
這個就要用到媒體查詢
方式一
@media print {
/* 此部分書寫的樣式還在列印時生效 */
}
方式二
<!-- 引入的css資源只在列印時生效 -->
<link rel="stylesheet" href="./css/print.css" media="print">
只需要在列印樣式中將無關內容進行隱藏即可
基本能做到1比1的還原
行動端
採用jsPDF + html2canvas
- html2canvas 負責將頁面轉為圖片
- jsPDF負責將圖片轉為PDF
function getBase64Image(img) {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
var dataURL = canvas.toDataURL("image/png");
return dataURL;
}
// 匯出pdf
// 當然這裡確保圖片資源被轉為了base64,否則匯出的簡歷無法展示圖片
html2canvas(document.getElementById('page').contentDocument.body).then(canvas => {
//返回圖片dataURL,引數:圖片格式和清晰度(0-1)
var pageData = canvas.toDataURL('image/jpeg', 1.0);
//方向預設豎直,尺寸ponits,格式a4[595.28,841.89]
var doc = new jsPDF('', 'pt', 'a4');
//addImage後兩個引數控制新增圖片的尺寸,此處將頁面高度按照a4紙寬高比列進行壓縮
// doc.addImage(pageData, 'JPEG', 0, 0, 595.28, 592.28 / canvas.width * canvas.height);
doc.addImage(pageData, 'JPEG', 0, 0, 595.28, 841.89);
doc.save(`${Date.now()}.pdf`);
});
但目前此種匯出方式還存在一些問題尚未解決,後續換用其它方案進行處理
- 不支援超連結
- 不支援iconfont
- 字型的留白部分會被剔除
小結
到這裡整個專案的雛形算完成了
- 導航欄切換簡歷模板
- 在JSON編輯器中改動
json
-> 頁面資料更新 - 匯出pdf
- 行動端 - jspdf
- 電腦 - 列印
高能操作
高亮變動的內容
訴求:在json編輯器中進行了內容的更新,期望能在簡歷中高亮展示出變動的內容
轉為技術需求就是期望能監聽到變動的dom,然後高亮
這個地方就用到 MutationObserver了
它提供了監視對DOM樹所做更改的能力
/**
* 高亮變化的Dom
*/
function initObserver() {
// 包含子孫節點
// 將監視範圍擴充套件至目標節點整個節點樹中的所有節點
// 監視指定目標節點或子節點樹中節點所包含的字元資料的變化
const config = { childList: true, subtree: true, characterData: true };
// 範例化監聽器物件
const observer = new MutationObserver(debounce(function (mutationsList, observer) {
for (const e of mutationsList) {
let target = e.target
if (e.type === 'characterData') {
target = e.target.parentElement
}
// 高亮
highLightDom(target)
}
}, 100))
// 監聽子頁面的body
observer.observe(document.getElementById('page').contentDocument.body, config);
// 因為 MutationObserver 是微任務,微任務後面緊接著就是頁面渲染
// 停止觀察變動
// 這裡使用宏任務,確保此輪Event loop結束
setTimeout(() => {
observer.disconnect()
}, 0)
}
function highLightDom(dom, time = 500, color = '#fff566') {
if (!dom?.style) return
if (time === 0) {
dom.style.backgroundColor = ''
return
}
dom.style.backgroundColor = '#fff566'
setTimeout(() => {
dom.style.backgroundColor = ''
}, time)
}
何時呼叫 initObserver
當然是在更新頁面之前的時候註冊事件,頁面完成變動渲染後停止監聽
function updatePage(data) {
// 非同步的微任務,本輪event loop結束停止觀察
initObserver()
// 同步
setSchema(data, getPageKey())
// 同步 + 渲染頁面
refreshIframePage()
}
效果
點哪改哪
期望效果
訴求:
- 點選需要修改的部分,就能進行修改操作
- 修改結果在簡歷上與json編輯器中進行內容同步
下面闡述一下實現
1. 獲取點選的Dom
document.getElementById('page').contentDocument.body.addEventListener('click', function (e) {
const $target = e.target
})
2. 獲取dom內容在頁面中出現的次數與相對位置
- 子頁面只包含展示邏輯,所以需要父頁面做hack操作才能在定位點選內容在json中對應位置
- 擁有相同內容的dom不止一個,所以需要全部找出來
/**
* 遍歷目標Dom樹,找出文字內容與目標一致的dom組
*/
function traverseDomTreeMatchStr(dom, str, res = []) {
// 如果有子節點則繼續遍歷子節點
if (dom?.children?.length > 0) {
for (const d of dom.children) {
traverseDomTreeMatchStr(d, str, res)
}
// 相等則記錄下來
} else if (dom?.textContent?.trim() === str) {
res.push(dom)
}
return res
}
// 監聽簡歷頁的點選事件
document.getElementById('page').contentDocument.body.addEventListener('click', function (e) {
const $target = e.target
// 點選的內容
const clickText = $target.textContent.trim()
// 只包含點選內容的節點
const matchDoms = traverseDomTreeMatchStr(document.getElementById('page').contentDocument.body, clickText)
// 點選的節點在 匹配的 節點中的相對位置
const mathIndex = matchDoms.findIndex(v => v === $target)
// 不包含則不做處理
if (mathIndex < 0) {
return
}
})
3. 獲取jsoneditor中對應的節點
- 與上面邏輯類似
- 先過濾出只包含此節點內容的幾個節點
- 然後根據點選dom在同內容節點列表中的相對位置進行匹配
// 監聽簡歷頁的點選事件
document.getElementById('page').contentDocument.body.addEventListener('click', function (e) {
// ...省略上述列出的程式碼
// 解除上次點選的dom高亮
highLightDom($textarea.clickDom, 0)
// 高亮這次的10s
highLightDom($target, 10000)
// 更新jsoneditor中的search內容
editor.searchBox.dom.search.value = clickText
// 主動觸發搜尋
editor.searchBox.dom.search.dispatchEvent(new Event('change'))
// 將點選內容顯示在textarea中
$textarea.value = clickText
// 自動聚焦輸入框
if (document.getElementById('focus').checked) {
$textarea.focus()
}
// 記錄點選的dom,掛載$textarea上
$textarea.clickDom = e.target
// jsoneditor 搜尋過濾的內容為模糊匹配,比如搜尋 a 會匹配 ba,baba,a,aa,aaa
// 根據上面得到的matchIndex,進行精確匹配全等的json節點
let i = -1
for (const r of editor.searchBox.results) {
// 全等得時候下標才變動
if (r.node.value === clickText) {
i++
// 匹配到json中的節點
if (i === mathIndex) {
// 高亮一下$textarea
$textarea.style.boxShadow = '0 0 1rem yellow'
setTimeout(() => {
$textarea.style.boxShadow = ''
}, 200)
return
}
}
// 手動觸發jsoneditor的next search match 按鈕, 切換jsoneditor中active的節點
editor.searchBox.dom.input.querySelector('.jsoneditor-next').dispatchEvent(new Event('click'))
// active的節點可以通過下面方式獲取
// editor.searchBox.activeResult.node
}
})
4. 更新節點內容
- 上面兩個步驟將簡歷中的dom與jsoneditor的dom都獲取到了
- 通過textarea輸入的內容
- 將輸入的內容分別更新到這兩個dom上,並把最新的json寫入的localStorage中
// 監聽輸入事件,並做一個簡單的防抖
$textarea.addEventListener('input', debounce(function () {
if (!editor.searchBox?.activeResult?.node) {
return
}
// 啟用dom變動事件
initObserver()
// 更新點選dom
$textarea.clickDom.textContent = this.value
// 更新editor的dom
editor.searchBox.activeResult.node.value = this.value
editor.refresh()
// 更新到本地
setSchema(editor.get(), getPageKey())
}, 100))
這樣就完成了兩側(簡歷/jsoneditor)資料的更新
後續規劃
- 接入更多的框架支援
- 優化pdf的匯出
- 超連結
- 字型圖示
- 優化使用者體驗
- 降低jsoneditor的存在感,當前的新增與刪除操作依賴jsoneditor,對不懂前端魔法的同學不友好
- 優化行動端的互動
- 美化介面
- 加入自動生成程式碼模板指令
- 接入更多的模板
相關文章