form-for-view
v1.0.10
Published
基于组件插槽自动生成表单并且自动回填数据构建页面的插件
Maintainers
Readme
formForView
基于组件插槽自动生成表单并且自动回填数据构建页面的 Vue 3 插件
功能特性
- 📝 自动表单生成: 根据插槽元素自动生成对应的表单控件
- 🎯 智能字段识别: 自动识别文本、图片、视频等不同类型的字段
- 💾 自动保存: 表单数据变化时自动保存到 localStorage
- 📚 历史记录: 支持查看和恢复历史记录
- 🛠️ 自定义工具条: 支持添加自定义工具按钮
- 🎨 Element Plus 集成: 基于 Element Plus 组件库
- 🎛️ 样式编辑: 支持在运行时调整类名或编辑字段对应的样式
安装
npm install form-for-view
# 或
yarn add form-for-view
# 或
pnpm add form-for-view快速开始
全局注册
import { createApp } from 'vue'
import FormForView from 'form-for-view'
const app = createApp(App)
app.use(FormForView)
app.mount('#app')导出方式
import FormForView, { FormForView as FormForViewComponent, PageForView } from 'form-for-view'default导出:插件安装入口,配合app.use(FormForView)使用FormForView/PageForView命名导出:组件本身,适合局部注册或按需引入
局部使用示例
<template>
<div id="app">
<h1>formForView 插件示例</h1>
<div class="mode-buttons">
<el-button @click="formType = 'edit'" :type="formType === 'edit' ? 'primary' : 'default'">
编辑模式
</el-button>
<el-button @click="formType = 'show'" :type="formType === 'show' ? 'primary' : 'default'">
页面模式
</el-button>
<el-button @click="isValidate = !isValidate" :type="isValidate ? 'primary' : 'default'">
开启校验
</el-button>
</div>
<div class="content-container" v-if="formType === 'show'">
<h2>PageForView 页面渲染示例</h2>
<PageForView :formData="formData" :style="styleMap" v-slot="{ ffvArray }">
<div class="header-section">
<h2 edit="title">
产品标题</h2>
<p edit="subtitle">产品副标题描述</p>
</div>
<el-carousel indicator-position="outside" edit="carousel" edit-type="array" array-limit="4">
<el-carousel-item v-for="(item, index) in ffvArray('carousel')" :key="index">
<h3 array-key="name" class="carousel-title">{{ item.name }}</h3>
<img array-key="src" alt="产品图片" class="carousel-image" :src="item.src">
</el-carousel-item>
</el-carousel>
<div class="image-section">
<img edit="mainImage" style="width:300px; height: auto;"
src="https://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960"
alt="产品图片" class="main-image">
</div>
<div edit="backgroundImage" edit-type="background-image" style="width: 300px;height: 200px;"></div>
<div class="description-section">
<h3>产品描述</h3>
<div edit="description">
这是一个产品描述文本,您可以在编辑模式下修改这些内容。
支持多行文本编辑,并且可以实时预览效果。
</div>
</div>
</PageForView>
</div>
<FormForView v-else :form-data="formData" :style="styleMap" :form-type="formType" :tools="tools" @save="handleSave" :rules="rules"
:is-validate="isValidate" @update:form-data="handleUpdate" @update:style="handleStyleUpdate" @upload="handleUpload" @choose-data="handleChooseData"
:autoTimes="30" v-slot="{ ffvArray, ffvData }">
<div class="content-container">
<div class="header-section">
<div v-for="(item, index) in ffvArray('List')" :key="index">
<el-checkbox :label="item.name" v-model="item.checked">{{ item.name }}</el-checkbox>
</div>
<h2 edit="title">产品标题</h2>
<p edit="subtitle">产品副标题描述</p>
<p edit="chooseDataid" edit-type="choosedata">{{ ffvData['chooseDataid'] }}</p>
</div>
<el-carousel indicator-position="outside" edit="carousel" edit-type="array" array-limit="4"
array-keys="name:text,src:file,chooseDataId:choosedata">
<el-carousel-item v-for="(item, index) in ffvArray('carousel')" :key="index">
<h3 text="2xl" justify="center">{{ item.name }}</h3>
<img alt="产品图片" class="carousel-image" :src="item.src">
<span> {{ item.chooseDataId }}</span>
</el-carousel-item>
</el-carousel>
<div class="image-section">
<img edit="mainImage" style="width:300px; height: auto;"
src="https://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960"
alt="产品图片" class="main-image">
</div>
<div edit="backgroundImage" edit-type="background-image" style="
background-image: url('https://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960');
width: 300px;height: 200px;">
</div>
<div class="description-section">
<h3>产品描述</h3>
<div edit="description">
这是一个产品描述文本,您可以在编辑模式下修改这些内容。
支持多行文本编辑,并且可以实时预览效果。
</div>
</div>
<div class="features-section">
<h3>产品特性</h3>
<ul>
<li edit="feature1">
特性一:高质量材料</li>
<li edit="feature2">特性二:精湛工艺</li>
<li edit="feature3">特性三:优质服务</li>
</ul>
</div>
<div class="media-section">
<h3>产品展示</h3>
<video edit="demoVideo" controls class="demo-video"
src="https://sample-videos.com/zip/10/mp4/SampleVideo_1280x720_1mb.mp4">
您的浏览器不支持视频播放。
</video>
</div>
<div class="price-section">
<span class="price-label">价格:</span>
<span edit="price" class="price-value">¥299.00</span>
</div>
</div>
</FormForView>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { FormForView, PageForView } from 'form-for-view'
const formType = ref('edit')
const isValidate = ref(true)
const styleMap = ref({})
const rules = ref({
title: [{ required: true, message: '请输入标题!!!!', trigger: 'blur' }, { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' }],
})
const List = ref([
{
name: '特性一:高质量材料',
},
{
name: '特性二:精湛工艺',
},
{
name: '特性三:优质服务',
}
])
const formData = ref({
List,
title: '智能家居控制器',
subtitle: '让您的家更智能、更舒适',
mainImage: 'https://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960',
backgroundImage: 'https://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960',
description: '这款智能家居控制器采用最先进的技术,支持语音控制、远程操控等多种功能。让您随时随地掌控家中的一切设备,享受智能生活带来的便利。',
feature1: 'AI语音控制,解放双手',
feature2: '远程控制,随时随地',
feature3: '节能环保,智能调节',
demoVideo: 'https://sample-videos.com/zip/10/mp4/SampleVideo_1280x720_1mb.mp4',
price: '¥399.00',
carousel: [
{
src: 'https://bpic.51yuansu.com/backgd/cover/00/07/60/5b6aebab85276.jpg?x-oss-process=image/resize,h_360,m_lfit/sharpen,100',
name: '产品图片1'
},
{
src: 'https://bpic.51yuansu.com/backgd/cover/00/07/60/5b6aebab85276.jpg?x-oss-process=image/resize,h_360,m_lfit/sharpen,100',
name: '产品图片2'
},
{
src: 'https://img2.baidu.com/it/u=2859622770,128426944&fm=253&fmt=auto&app=138&f=JPEG?w=1313&h=500',
name: '产品图片3'
}
]
})
const tools = ref([
{
name: '预览',
call: (data) => {
console.log('当前数据预览:', data)
alert('数据已输出到控制台')
}
},
{
name: '重置',
call: (data) => {
if (confirm('确定要重置所有数据吗?')) {
formData.value = {
title: '产品标题',
subtitle: '产品副标题描述',
mainImage: 'https://trae-api-sg.mchost.guru/api/ide/v1/text_to_image?prompt=modern%20product%20showcase%20with%20clean%20background&image_size=square_hd',
description: '这是一个产品描述文本,您可以在编辑模式下修改这些内容。',
feature1: '特性一:高质量材料',
feature2: '特性二:精湛工艺',
feature3: '特性三:优质服务',
demoVideo: 'https://sample-videos.com/zip/10/mp4/SampleVideo_1280x720_1mb.mp4',
price: '¥299.00'
}
}
}
},
{
name: '导出',
call: (data) => {
const jsonStr = JSON.stringify(data, null, 2)
const blob = new Blob([jsonStr], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'form-data.json'
a.click()
URL.revokeObjectURL(url)
}
}
])
const handleSave = (data) => {
console.log('保存数据:', data)
// 这里可以添加保存到后端的逻辑
}
const handleUpload = (callBack) => {
console.log('上传文件回调触发')
// 这里可以添加上传文件到后端的逻辑
// 上传完成后,调用回调函数并传递文件URL
callBack('https://bpic.51yuansu.com/backgd/cover/00/07/60/5b6aebab85276.jpg?x-oss-process=image/resize,h_360,m_lfit/sharpen,100')
}
const handleChooseData = (callBack) => {
console.log('选择数据回调触发')
// 这里可以添加上传文件到后端的逻辑
// 上传完成后,调用回调函数并传递文件URL
callBack(1)
}
const handleUpdate = (data) => {
console.log('更新数据:', data)
formData.value = data
}
const handleStyleUpdate = (data) => {
styleMap.value = { ...data }
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.mode-buttons {
margin-bottom: 30px;
text-align: center;
}
.mode-buttons .el-button {
margin: 0 10px;
}
.content-container {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
padding: 40px;
margin-top: 20px;
}
.header-section {
text-align: center;
margin-bottom: 40px;
}
.header-section h2 {
font-size: 32px;
color: #303133;
margin-bottom: 10px;
}
.header-section p {
font-size: 18px;
color: #606266;
}
.image-section {
text-align: center;
margin-bottom: 40px;
}
.main-image {
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.description-section {
margin-bottom: 40px;
}
.description-section h3 {
font-size: 24px;
color: #303133;
margin-bottom: 15px;
}
.description-section div {
font-size: 16px;
line-height: 1.6;
color: #606266;
}
.features-section {
margin-bottom: 40px;
}
.features-section h3 {
font-size: 24px;
color: #303133;
margin-bottom: 15px;
}
.features-section ul {
list-style: none;
padding: 0;
}
.features-section li {
font-size: 16px;
color: #606266;
margin-bottom: 10px;
padding-left: 20px;
position: relative;
}
.features-section li:before {
content: "✓";
position: absolute;
left: 0;
color: #67c23a;
font-weight: bold;
}
.media-section {
margin-bottom: 40px;
text-align: center;
}
.media-section h3 {
font-size: 24px;
color: #303133;
margin-bottom: 15px;
}
.demo-video {
max-width: 100%;
height: auto;
border-radius: 8px;
}
.price-section {
text-align: center;
padding: 20px;
background: #f5f7fa;
border-radius: 8px;
}
.price-label {
font-size: 18px;
color: #606266;
margin-right: 10px;
}
.price-value {
font-size: 32px;
font-weight: bold;
color: #f56c6c;
}
</style>API 参考
Props
| 参数名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | formData | Object | {} | 表单数据对象 | | style | Object | {} | 可选的样式映射对象,传入后会作用于渲染结果 | | rules | Object | {} | 表单验证规则对象 | | formType | String | 'show' | 表单类型: 'edit'(编辑模式)、'show'(页面模式) | | tools | Array | [] | 工具条配置,每个元素包含 name 和 call 属性 | | isValidate | Boolean | false | 是否开启校验 | | autoTimes | Number | 0 | 自动保存时间间隔,单位秒(0 表示不自动保存) |
Events
| 事件名 | 说明 | 参数 | |--------|------|------| | save | 保存表单数据时触发 | (formData: Object) | | upload | 上传文件时触发 | (callBack: (url: string) => void) | | choose-data | 选择数据时触发 | (callBack: (result: unknown) => void) | | update:form-data | 表单数据更新时触发 | (formData: Object) | | update:style | 样式编辑应用后触发 | (styleMap: Record<string, Record<string, string>>) |
插槽指令
在插槽元素上使用 edit 指令来标记可编辑字段:
在插槽元素上使用 edit-type 指令来指定编辑类型:
text(默认):普通文本字段background-image:背景图片字段array:数组数据容器(配合子元素使用array-key标注键)choosedata:选择数据字段(需配合choose-data事件配置)
<!-- 文本字段 -->
<p edit="title">标题文本</p>
<div edit="content">内容文本</div>
<!-- 图片字段 -->
<img edit="avatar" src="default.jpg" alt="头像">
<!-- 媒体文件 -->
<video edit="video" src="default.mp4"></video>
<audio edit="audio" src="default.mp3"></audio>进阶用法
数组模式
- 容器属性
edit:数组字段名,例如carouseledit-type="array":声明为数组容器array-limit:可选,限制数组最大项数array-keys:可选,指定键的类型映射(JSON 对象或逗号分隔key:type) 例:name:text,src:file,chooseDataid:choosedata
- 子元素属性
array-key:标注该元素对应的键名,例如name、src
在 FormForView 中使用
<FormForView :form-data="formData" :form-type="formType" @upload="handleUpload" v-slot="{ ffvArray }">
<el-carousel indicator-position="outside" edit="carousel" edit-type="array" array-limit="4" array-keys="name:text,src:file">
<el-carousel-item v-for="(item, index) in ffvArray('carousel')" :key="index">
<h3>{{ item.name }}</h3>
<img :src="item.src" />
</el-carousel-item>
</el-carousel>
</FormForView>- 编辑模式交互
- 数组以
el-tabs渲染,每项一个标签页,支持新增/删除(受array-limit限制) text/textarea/file键类型自动匹配控件;file键提供上传按钮- 快速模式下,双击数组容器会弹出对话框,使用
el-tabs编辑整组数据
- 数组以
在 PageForView 中使用
<PageForView :formData="formData" v-slot="{ ffvArray }">
<el-carousel indicator-position="outside" edit="carousel" edit-type="array">
<el-carousel-item v-for="(item, index) in ffvArray('carousel')" :key="index">
<h3 array-key="name">{{ item.name }}</h3>
<img array-key="src" :src="item.src" />
</el-carousel-item>
</el-carousel>
</PageForView>背景图片 (edit-type="background-image")
<div edit="bg" edit-type="background-image" style="height: 200px"></div>样式编辑
- 编辑模式下可通过工具条或元素浮层打开样式编辑面板
- 面板支持切换目标类名,并通过
style映射作用到FormForView/PageForView - 样式变更会通过
update:style事件抛出,便于业务侧持久化
<FormForView
:form-data="formData"
:style="styleMap"
form-type="edit"
@update:style="handleStyleUpdate"
>
<div class="hero-title" edit="title">标题</div>
</FormForView>
<PageForView :formData="formData" :style="styleMap">
<div class="hero-title" edit="title">标题</div>
</PageForView>const styleMap = ref({})
const handleStyleUpdate = (nextStyleMap) => {
styleMap.value = { ...nextStyleMap }
}自定义控件(规划)
- 支持通过属性扩展自定义编辑类型与渲染策略
- 建议结合
rules与业务校验在表单侧进行增强
常见问题
- 上传事件签名:统一为
(callBack: (url: string) => void),示例与文档已更新 - 选择数据事件签名:推荐按
(callBack: (result: unknown) => void)理解,业务侧可传字符串、数字或对象 - 插件安装方式:
app.use()请使用默认导出,命名导出用于局部组件注册 - edit-type 命名:统一为中划线小写(如
background-image、array),兼容旧值backgroundImage - 表单校验:请优先使用组件
rulesprop,不再推荐在插槽节点上写rule - 文本换行显示:建议在页面样式中为文本元素设置
white-space: pre-line以识别\n - 样式编辑:样式通过
styleMap作用到FormForView / PageForView,若需要持久化,请监听update:style
SSR 与 Client-Only
PageForView可用于 SSR 场景(纯数据渲染路径)FormForView包含编辑态能力(历史记录、浮层、样式注入、DOM 扫描),建议仅在客户端渲染
Nuxt 使用示例
<template>
<ClientOnly>
<FormForView :form-data="formData" form-type="edit" @choose-data="handleChooseData" />
</ClientOnly>
</template>PHP 模板场景
- 如果前台页面不是 Vue,而是 PHP 模板渲染,可使用实验性的 PHP SDK
- SDK 提供服务端版
PageForView,接收formData + styleMap输出最终 HTML - 适合 SEO 页面、CMS 模板页、服务端首屏渲染场景
相关文件:
CDN 使用
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
<link rel="stylesheet" href="https://unpkg.com/form-for-view/dist/index.css" />
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/element-plus"></script>
<script src="https://unpkg.com/@element-plus/icons-vue"></script>
<script src="https://unpkg.com/form-for-view/dist/index.umd.js"></script><script>
const app = Vue.createApp({})
app.use(ElementPlus)
const ffv = (window.FormForView && window.FormForView.default) || window.FormForView
app.use(ffv)
app.mount('#app')
</script>- 在浏览器全局环境下,优先使用
window.FormForView.default - 若后续导出策略调整,
window.FormForView兜底写法可保持兼容 - 可直接使用
form-for-view/page-for-view标签
UMD 导出形态评估(P2)
- 当前 UMD 为
named + default,兼容 npm/ESM 与 CDN 双场景 - 若改为 default-only,CDN 用法会更直观,但会影响 named 导出语义与现有接入方
- 现阶段建议维持现状,并统一推荐如下全局安装方式:
const ffv = (window.FormForView && window.FormForView.default) || window.FormForView
app.use(ffv)模式说明
编辑模式 (edit)
- 显示工具条,包含编辑、退出、历史记录和自定义工具按钮
- 点击编辑按钮弹出抽屉表单,宽度为 50%
- 表单控件聚焦时同步高亮对应页面元素
- 支持文件上传功能
- 支持样式编辑抽屉
- 自动保存功能
页面模式 (show)
- 仅显示静态内容
- 无编辑功能
- 适合最终展示
PageForView 组件
- 用于渲染数据的页面组件
- 支持自定义数据展示模板
<template>
<PageForView :formData="formData">
<h1 edit="title">默认标题</h1>
<img edit="banner" />
<p edit="desc">默认描述</p>
<div edit="bg" edit-type="background-image" style="height: 200px"></div>
</PageForView>
</template>
<script setup>
import { PageForView } from 'form-for-view'
const formData = {
title: '产品标题',
banner: 'https://example.com/banner.png',
desc: '这里是产品描述文本',
bg: 'https://example.com/bg.jpg'
}
</script>历史记录
- 自动保存每次修改到 localStorage
- 支持查看最近 10 条历史记录
- 点击历史记录可恢复到对应状态
依赖
- Vue 3.x
- Element Plus 2.x
- @element-plus/icons-vue
License
更新日志
1.0.9
- 补充 SSR / CDN 使用说明与 smoke 示例
- 样式编辑面板升级为侧边抽屉,支持样式编辑与样式预览切换
- 统一事件名称为 kebab-case(如
choose-data、update:form-data) - 修复内联操作清理逻辑与若干交互问题
1.0.0
- 初始版本发布
- 支持编辑模式、快速模式、页面模式
- 自动生成表单控件并支持数据回填
1.0.1
- 按需引入与注册 Element Plus 组件与样式,移除全量引入,显著降低包体积
- 文档文案 和用例错误调整
1.0.3
- 新增 PageForView 组件,用于渲染数据
1.0.4
- 导出bug修改
1.0.5
- 新增
edit-type="array"在PageForView中的完整支持,插槽注入ffvArray FormForView默认插槽注入ffvArray/ffvData,兼容第三方容器延迟渲染- 数组编辑 UI 改为
el-tabs,支持新增/删除、上限array-limit - 快速编辑支持数组整项编辑,对话框中以
el-tabs管理各项
1.0.6
- 一些优化和调整
1.0.7
- 上传回调增加 保存机制会触发保存事件
- 快速选中框增加了 按钮组
- 新增
rulesprop 用于配置表单验证规则 参考elementui的验证 - 删除了行间的 rule 属性,统一通过 prop 配置(尽量较少对原结构的修改)
1.0.8
- 调整了快速编辑模式的高亮蒙层追加位置
- 调整了按需引入与注册 Element Plus 组件 避免控制台多次注册的警告
- 新增
edit-type="choosedata",插槽注入ffvArray使用 - 新增
choose-data事件,用于选择数据时触发
