В этом кейсе объясняется, как использовать vue.js + ElementUI для разработки простогоДемонстрация настраиваемой комбинированной формы.
Пример исходного кодаgithub
Демонстрация в действии (увеличенный GIF):
Создайте новый блок формы слева, выберите заголовок блока и тип компонента формы и нажмите ОК.Новая форма будет сгенерирована в средней области, а результаты объединения данных всех форм будут отображаться справа.
В этом примере вы можете в основном увидеть применение следующих точек знаний:
- компонент одного файла vue.js,
- проход компонента
- пользовательская v-модель
- мониторинг данных
- слияние данных
- Автоматически регистрировать компоненты в пакетах
- Используйте примеси для извлечения общего кода
- дерзкий синтаксис
- Спецификация БЭМ
- Старайтесь избегать использования циклов for
-
<component>
компоненты - Динамически привязывать v-модель к набору данных
Они перечислены выше, потому что некоторые друзья в группе ранее спрашивали о связанных методах реализации. Они перечислены здесь. Возможно, вы, читающие эту статью, уже освоили их. Поздравляем! (Причиной этой статьи также является вопрос от группы друзей)
Начните текст ниже
Обзор
Если все компоненты и логика этой демонстрации будут записаны в файле, это будут сотни строк, которые будет сложно поддерживать, поэтому сначала создайте такую структуру каталогов:
Создайте базовую структуру
Чтобы быстро разрабатывать страницы, этот проект использует ElementUI и D2Admin для быстрой сборки. Все компоненты в следующих примерах взяты из этих двух проектов с открытым исходным кодом. Если вы не знаете эти компоненты, не имеет значения, просто поймите имея в виду.
Сначала напишите общую структуру страницы:
<template>
<d2-container>
<template slot="header">可配置问卷示例</template>
<div class="questionnaire">
<el-container>
<!-- 左侧位置 -->
<!-- 中间位置 -->
<!-- 右侧位置 -->
</el-container>
</div>
<template slot="footer">从左侧选择要添加的表单块,右侧查看结果</template>
</d2-container>
</template>
<script>
export default {
name: 'page1',
components: {
// 这里以后要要注册表单区块 左侧边栏 右侧边栏
},
data () {
return {
formList: [], // 所有注册的表单区块
forms: [] // 用户已经选择的表单区块
}
}
}
</script>
css/sass пока будут игнорироваться, а код стиля будет отображаться в конце
блок формы
новыйpage1/components/Form/Form1.vue
как первый блок формы
<template>
<el-form ref="form" :model="form" label-position="top">
<el-form-item label="姓名">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="姓名">
<el-radio-group v-model="form.usersex">
<el-radio :label="1">男</el-radio>
<el-radio :label="0">女</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: 'Form1',
props: {
value: {
default: () => ({
username: '',
usersex: 1
})
}
},
data () {
return {
form: {
username: '',
usersex: 1
}
}
},
watch: {
form: {
// 处理方法
handler (value) {
this.$emit('input', value)
},
// 深度 watch
deep: true,
// 首先自己执行一次
immediate: true
}
}
}
</script>
Это очень простая форма, созданная с помощью ElementUI, даже без проверки.
Затем мы регистрируем этот блок формы на компоненте страницы:
<script>
components: {
// 注册组件
Form1: () => import('./components/Form/Form1.vue')
},
data () {
return {
// 注册到数据
formList: [
{
title: '基础',
name: 'Form1'
}
]
}
}
</script>
Подождите, если у меня 20 блоков, я должен 20 раз прописывать регистрацию и вручную добавлять 20 объектов в formList?
Итак, мы сначала создали 7 новых блоков, содержимое блоков похоже, а код немного изменен:
Пример блока формы
<template>
<el-form ref="form" :model="form" label-position="top">
<el-form-item label="姓名">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="姓名">
<el-radio-group v-model="form.usersex">
<el-radio :label="1">男</el-radio>
<el-radio :label="0">女</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</template>
<script>
export default {
// 排序使用
index: 1,
// 组件标题
title: '基础',
// 组件名
name: 'Form1',
props: {
value: {
default: () => ({
username: '',
usersex: 1
})
}
},
data () {
return {
form: {
username: '',
usersex: 1
}
}
},
watch: {
form: {
handler (value) {
this.$emit('input', value)
},
deep: true,
immediate: true
}
}
}
</script>
Компоненты страницы (показывать только ключевые части)
<script>
import sortby from 'lodash.sortby'
const req = context => context.keys().map(context)
const forms = req(require.context('./components/Form/', false, /\.vue$/))
const components = {}
const formList = []
sortby(forms.map(e => {
const component = e.default
const { index, title, name } = component
return { component, title, index, name }
}), ['index']).forEach(form => {
const { component, title, name } = form
components[name] = component
formList.push({ title, name })
})
export default {
components,
data () {
return {
formList
}
}
}
</script>
Вы спросите, а что, черт возьми, это за большая глыба наверху? ? ?
Сначала представьте веб-пакетыrequire-contextВы можете нажать на ссылку, чтобы просмотреть официальную документацию.
Говоря простым и популярным языком, этот метод используется для облегчения внедрения большого количества файлов, он получает три параметра.
- Каталог, в который вы хотите импортировать файл
- найти ли подкаталоги в этом каталоге
- соответствовать импортируемому файлу
Затем он вернет требуемый объект, объект имеет три свойства: разрешение, ключи, идентификатор
- разрешить: это функция, которая возвращает идентификатор разрешенного модуля
- keys: также является функцией, которая возвращает массив, состоящий из всех объектов запроса, которые могут быть проанализированы модулем контекста.
- id: идентификатор контекстного модуля
Итак, в приведенном выше коде
const req = context => context.keys().map(context)
const forms = req(require.context('./components/Form/', false, /\.vue$/))
Окончательная форма./components/Form/
Все объекты файла vue в каталоге
затем пройти
sortby(forms.map(e => {
const component = e.default
const { index, title, name } = component
return { component, title, index, name }
}), ['index']).forEach(form => {
const { component, title, name } = form
components[name] = component
formList.push({ title, name })
})
Обработайте объект формы, получите формат компонентов, требуемый vue для регистрации компонентов, и сохраните всю информацию о компонентах в formList для использования в логике страницы. Пожалуйста, обратитесь к приведенному выше коду для конкретного метода преобразования.
поэтому независимо от того, где мы находимся./components/Form/
Сколько бы однофайловых компонентов ни было записано, webpack автоматически поможет нам внедрить и зарегистрировать их на странице через наш код.
Проблема большого количества регистраций компонентов решена, и теперь у нас есть еще одна проблема, которую нужно оптимизировать:
Будь то Form1, Form2 или FormN, вы обнаружите, что в коде есть повторяющийся контент, а некоторые логически связаны с повторяющимся контентом.Давайте напишем миксин, чтобы уменьшить повторяющийся код:
Суеверие.
export default function (form) {
return {
props: {
value: {
default: () => form
}
},
data () {
return {
form
}
},
watch: {
form: {
handler (value) {
this.$emit('input', value)
},
deep: true,
immediate: true
}
}
}
}
Этот файл js экспортирует функцию, которая принимает параметр формы, присваивает этот параметр свойству значения и полю формы в данных и возвращает объект.
Затем мы регистрируем этот миксин в каждом компоненте формы и модифицируем каждый компонент формы:
<template>
<el-form ref="form" :model="form" label-position="top">
<el-form-item label="姓名">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="姓名">
<el-radio-group v-model="form.usersex">
<el-radio :label="1">男</el-radio>
<el-radio :label="0">女</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</template>
<script>
import mixin from './mixin'
export default {
index: 1,
title: '基础',
name: 'Form1',
mixins: [
mixin({
username: '',
usersex: 1
})
]
}
</script>
Таким образом, каждый компонент формы сохраняет дюжину строк кода, главное, чтобы эти коды повторялись и были избыточными.
Последний компонент — это страница, подобная этой:
<template>
<d2-container>
<template slot="header">
可配置问卷示例
</template>
<div class="questionnaire">
<el-container>
<aside-left
:all="formListUseful"
:selected="forms"
@select="handleAsideSelect"
@remove="handleAsideRemove"/>
<el-main class="questionnaire__main">
<div class="questionnaire__container">
<el-card
v-for="(form, index) in forms"
:key="index"
shadow="never"
class="questionnaire__card">
<template slot="header">
{{form.title}}
</template>
<div style="margin-bottom: -20px;">
<component
:is="form.name"
v-model="forms[index].data"/>
</div>
</el-card>
</div>
</el-main>
<aside-right :res="res"/>
</el-container>
</div>
<template slot="footer">
从左侧选择要添加的表单块,右侧查看结果
</template>
</d2-container>
</template>
<script>
import sortby from 'lodash.sortby'
const req = context => context.keys().map(context)
const forms = req(require.context('./components/Form/', false, /\.vue$/))
const components = {}
const formList = []
sortby(forms.map(e => {
const component = e.default
const { index, title, name } = component
return { component, title, index, name }
}), ['index']).forEach(form => {
const { component, title, name } = form
components[name] = component
formList.push({ title, name })
})
export default {
name: 'page1',
components: {
...components,
AsideLeft: () => import('./components/AsideLeft'),
AsideRight: () => import('./components/AsideRight')
},
data () {
return {
formList,
forms: []
}
},
computed: {
// 合并最后结果
res () {
return Object.assign({}, ...this.forms.map(e => e.data))
},
formListUseful () {
return this.formList.filter(e => !this.forms.find(f => f.name === e.name))
}
},
methods: {
handleAsideSelect (val) {
this.forms.push({
...val
})
},
handleAsideRemove (index) {
this.forms.splice(index, 1)
}
}
}
</script>
<style lang="scss">
@import '~@/assets/style/public.scss';
.questionnaire {
@extend %full;
.el-container {
@extend %full;
}
.questionnaire__aside--left {
border-right: 1px solid #cfd7e5;
padding: 20px;
}
.questionnaire__aside--right {
border-left: 1px solid #cfd7e5;
padding: 20px;
.questionnaire__res-key {
font-size: 12px;
line-height: 14px;
color: $color-text-sub;
}
.questionnaire__res-value {
font-size: 14px;
line-height: 20px;
color: $color-text-normal;
margin-bottom: 10px;
}
}
.questionnaire__main {
background-color: rgba(#000, .05);
}
.questionnaire__container {
max-width: 400px;
margin: 0px auto;
.questionnaire__card {
border: 1px solid #cfd7e5;
margin-bottom: 20px;
.el-form-item__label {
line-height: 16px;
}
}
}
}
</style>
компонент левой страницы
Левый и правый компоненты не являются ключевым содержимым, поэтому код с комментариями отображается одновременно
новыйpage1/components/AsideLeft/index.vue
как компонент левой страницы
<template>
<el-aside
width="200px"
class="questionnaire__aside--left">
<!-- 已经选择的区块列表 点击每个按钮后开始删除响应的区块 -->
<div
v-for="(item, index) in selected"
:key="index"
class="d2-mb-10">
<el-button
@click="handleRemove(item, index)"
style="width: 100%;">
{{item.title}}
</el-button>
</div>
<!-- 新建区块按钮 -->
<div>
<el-button
type="primary"
style="width: 100%;"
@click="dialogVisible = true">
<d2-icon name="plus"/> 新增
</el-button>
</div>
<!-- 选择区块界面 -->
<el-dialog
title="选择区块"
:append-to-body="true"
:close-on-click-modal="false"
:visible.sync="dialogVisible">
<p class="d2-mt-0">区块标题</p>
<el-input v-model="title"></el-input>
<p>区块组件</p>
<el-alert
v-if="all.length === 0"
type="error"
title="没有可用区块"/>
<el-radio-group
v-else
v-model="name"
size="small">
<el-radio-button
v-for="(item, index) in all"
:key="index"
:label="item.name">
{{item.title}}
</el-radio-button>
</el-radio-group>
<span slot="footer">
<el-button
@click="dialogVisible = false">
取 消
</el-button>
<!-- 如果没有区块可用 不显示确定按钮 -->
<el-button
v-if="all.length !== 0"
type="primary"
@click="handleSelect">
确 定
</el-button>
</span>
</el-dialog>
</el-aside>
</template>
<script>
export default {
name: 'AsideLeft',
data () {
return {
// 新建区块的 dialog 显示控制
dialogVisible: false,
// 新建区块时设置的区块标题
title: '新区块',
// 新建区块时选择的区块
name: ''
}
},
props: {
// 所有可选区块
all: {
default: () => []
},
// 用户已经选择的区块
selected: {
default: () => []
}
},
watch: {
// 用户选择一个区块后,标题自动改为这个区块的默认标题
name (value) {
this.title = this.all.find(e => e.name === value).title
}
},
methods: {
// 用户选择区块完毕
handleSelect () {
// 关闭 dialog
this.dialogVisible = false
// 发送事件
this.$emit('select', {
name: this.name,
title: this.title,
data: {}
})
},
// 用户删除区块
handleRemove (item, index) {
this.$confirm(`删除 "${item.title}" 区块吗`, '确认操作', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 发送事件
this.$emit('remove', index)
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
}
}
</script>
компонент правой страницы
Левый и правый компоненты не являются ключевым содержимым, поэтому код с комментариями отображается одновременно
новыйpage1/components/AsideRight/index.vue
Как правая сторона компонентов страницы
<template>
<el-aside
width="200px"
class="questionnaire__aside--right">
<div
v-for="(item, index) in reslist"
:key="index">
<div
class="questionnaire__res-key">
{{item.keyName}}
</div>
<div
class="questionnaire__res-value">
{{item.value === '' ? '未填写' : item.value}}
</div>
</div>
</el-aside>
</template>
<script>
export default {
props: {
// 接收表单结果
res: {
default: () => ({})
}
},
computed: {
// 处理数据格式
reslist () {
return Object.keys(this.res).map(keyName => ({
keyName,
value: this.res[keyName]
}))
}
}
}
</script>
Весь код закончился, по факту мы написали пять файлов
- компонент страницы
- две боковые панели
- блок формы
- Миксин блока формы
Это небольшой пример, который требует больших знаний.Если у вас есть какие-либо сомнения относительно приведенного выше кода, вы можете задать вопросы в группе обмена QQ D2 Projects по телефону 806395827.
Эта статья была впервые опубликована на официальном публичном аккаунте команды проекта с открытым исходным кодом D2 D2 Projects.
Ссылаться на
адрес | описывать |
---|---|
Колонка самородков | Колонка самородков |
Домашняя страница команды | Домашняя страница команды открытого исходного кода |
Документация D2Admin на китайском языке | китайский документ |
Адрес предварительного просмотра D2Admin | Адрес предварительного просмотра полной версии |
D2Admin github | Репозиторий полной версии Github |
ElementUI | Библиотека компонентов ElementUI |