предисловие
Месяц назад я написал статьюКак написать хорошее резюме для набора в онлайн-школу, цель состоит в том, чтобы помочь учащимся, которые собираются начать подавать заявки на набор в школу, улучшить свои резюме.
Флаг также установлен в статье
Посмотреть на Githubзафиксировать запись, потребовалось около недели, чтобы придумать план, задуманный в моем сердце. Он может быть не идеальным, но я думаю, что он должен помочь некоторым ученикам.
Конечно, хорошие вещи отображаются три раза, O(∩_∩)O~~
Если вас не устраивает стиль шаблона (цвет, верстка), разберитесьПередняя магиястудентов могут клонироватьсклад, бросьте свое волшебное украшение
Студенты, заинтересованные в проекте, также приветствуютсяспособствоватьВзгляните на свой любимый шаблон резюме (код). Теоретически нет предела стеку технологий разработки. Конечно, вопросы или предложения также приветствуются.
В этой статье в основном рассказывается о дизайнерских идеях, технических решениях и некоторых проблемах, с которыми столкнулись, и решениях этого проекта (с использованием большого количества хакерских навыков), а также последующем планировании.
Дизайн проекта
макет
Базовая структура страницы всего приложения
<body>
<header>
<!-- 导航 -->
<nav></nav>
</header>
<div>
<!-- 展示简历 -->
<iframe></iframe>
<!-- 控制区域 -->
<div></div>
</div>
</body>
Некоторые друзья могут задаться вопросом, почему здесь используется iframe?
Здесь я дам вам краткое введение, и я объясню его вам позже, когда я буду говорить о техническом решении.
Раздел резюме в моем виденииПоказывать только логику, которую можно рассматривать как независимую статическую страницу
Поскольку это только для отображения, независимо от того, какая внешняя магия может выполнять эту работу, поэтому, чтобы облегчить использование заклинаний различными магами, эта часть отделена.Составителям шаблона резюме нужно только заботиться о том, как восстановить статическая страница. Остальная логика взаимодействия передается на родительскую страницу для унифицированной обработки
Технический отбор
Vanilla JS — самый легкий в мире JavaScript-фреймворк (никто) ----родной js
Основная часть всего приложения реализована на нативном js
Часть отображения CV дистальная теоретически любая методика может быть использована для достижения стека, связанного с низкой родительской страницей
коммуникация
- Переключайтесь между различными шаблонами резюме с помощью панели навигации.
- Изменения в резюме автоматически синхронизируются с описанием страницы в области управления
- Измените информацию описания страницы в области управления и обновите содержимое резюме в режиме реального времени.
описать резюме
- Используйте 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 в этом каталоге будут автоматически добавлены в запись веб-пакета и автоматически внедрены в текущий шаблон страницы.
Например
./src
├── pages
│ └── xxx
│ └───── index.html
│ └───── index.scss
│ └───── index.js
Автоматически генерировать конфигурацию записи/страницы здесьКод можно перенестикздесьПроверить
Автоматически сгенерированные результаты следующие
Формат содержимого каждого плагина HTMLWebpackPlugin выглядит следующим образом.
Автоматически генерировать панель навигации
В верхней части главной страницы есть панель навигации для переключения маршрута шаблона резюме.
Содержание ссылки в этой части очень скучно, если вы заполняете его вручную.Как добиться автоматической генерации?
Во-первых, содержимое навигационной части заголовка шаблона домашней страницы
<header>
<nav id="nav">
<%= htmlWebpackPlugin.options.pageNames %>
</nav>
</header>
htmlWebpackPlugin.options
выражатьHTMLWebpackPlugin
объектыuserOptions
Атрибуты
Мы получили заголовки всех страниц выше и используем все заголовки,
соединения сшиваются вместе, а затем связываются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
- Поскольку каждый маршрут переключения вызывает событие ONLOAD iFrame.
- Так что потратьте время, чтобы редактор обновил содержимое json здесь.
function getPageKey() {
return document.getElementById('page').contentWindow.location.pathname.replace(/\/$/, '')
}
document.getElementById('page').onload = function (e) {
// 更新editor中显示的内容
editor.set(getSchema(getPageKey()))
}
Написание шаблонных страниц
Вот 4 способа получить одну и ту же страницу
желаемый эффект
файл описания
Создайте файл описания 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>
Начните писать код ниже
с родительской страницейЕдинственная адекватная логикаНеобходимо смонтировать метод обновления в окне дочерней страницы, чтобы родительская страница активно вызывала обновление.
родной 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': '开发示例'
}
Обновление подстраницы
Существует предыдущее при создании экземпляра редактораupdatePage
метод
Если у подстраницы есть метод обновления, вызовите его напрямую, чтобы обновить страницу.Конечно, родительская страница будет хранить последние данные в 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
Сторона ПК
Во-первых, браузер ПК поддерживает печать и экспорт pdf.
Как запустить печать?
- Правая кнопка мыши и выберите печать
- Ярлык Ctrl + P
window.print()
Мы используем третью схему в коде здесь
Как я могу убедиться, что печатается только раздел резюме?
Это использование медиа-запросов
метод первый
@media print {
/* 此部分书写的样式还在打印时生效 */
}
Способ 2
<!-- 引入的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`);
});
Однако в этом методе экспорта все еще есть некоторые проблемы, которые еще не решены, и в будущем будут использоваться другие решения.
- Гиперссылки не поддерживаются
- шрифт значков не поддерживается
- Пробел шрифта будет удален
резюме
На этом прототип всего проекта готов.
- Панель навигации Переключить шаблон резюме
- Изменение в редакторе JSON
json
-> обновление данных страницы - экспортировать pdf
- Мобильный — jspdf
- Компьютер - Печать
высокоэнергетическая работа
Выделить измененный контент
Требование: В редакторе json обновлен контент, и ожидается, что измененный контент можно будет выделить в резюме.
Обращаясь к ТЗ, стоит ожидать мониторить измененный дом, а потом подсвечивать
Используйте это место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. Получить кликабельный Дом
document.getElementById('page').contentDocument.body.addEventListener('click', function (e) {
const $target = e.target
})
2. Получите количество и относительное положение содержимого dom на странице.
- Подстраница содержит только логику отображения, поэтому родительская страница должна выполнить операцию взлома, чтобы найти соответствующую позицию содержимого клика в json.
- Дом с таким же контентом - это более одного, поэтому вам нужно все это найти.
/**
* 遍历目标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
- Аналогично логике выше
- Сначала отфильтруйте несколько узлов, которые содержат только содержимое этого узла.
- Затем сопоставьте в соответствии с относительным положением щелкнутого дома в том же списке узлов контента.
// 监听简历页的点击事件
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. Обновить содержимое узла
- Вышеуказанные два шага получают как дом в резюме, так и дом jsoneditor.
- Контент, введенный через textarea
- Обновите входное содержимое до двух домов соответственно и запишите последний 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))
На этом обновление данных с обеих сторон завершено (resume/jsoneditor).
Последующее планирование
- Доступ к дополнительной поддержке фреймворка
- Оптимизировать экспорт pdf
- Гиперссылка
- значок шрифта
- Оптимизируйте пользовательский опыт
- Уменьшите присутствие jsoneditor.Текущие операции добавления и удаления основаны на jsoneditor, что не очень удобно для студентов, не понимающих магию внешнего интерфейса.
- Оптимизация взаимодействия на мобильных устройствах
- Украсить интерфейс
- Добавить директиву автоматически сгенерированного шаблона кода
- Перемещение большего количества шаблонов резюме
Спасибо, настаиваю на том, чтобы прочитать это, спасибо, что присоединились, если вы заинтересованы, добро пожаловать в свой свой вклад (CV / Функция)
Ссылки по теме
Прием на работу
Мейтуан21 весенний набор в школуОткрыто, всем радыДоставка
Автор находится вГруппа компаний "Прибытие" - Департамент технологий платформ, добро пожаловать присоединиться