基於Vue和Quasar的前端SPA項目實戰之拖拽表單定制(十六)
回顧
通過前一篇文章 基於Vue和Quasar的前端SPA項目實戰之動態表單(五)的介紹,實現了元數據中動態表單設計功能,支持常見的數據類型和索引,然后實現了動態表單的crud增刪改查功能,所有的表單頁面都是默認的風格。本文主要介紹拖拽表單定制功能,通過拖拽的方式定制表單錄入和編輯頁面,滿足了個性化需求。
簡介
針對元數據表的每個字段,通過拖拽方式決定是否顯示或者隱藏,然后還可以配置顯示的寬度。最終以json格式保存到后台數據庫,運行時根據配置動態渲染錄入和編輯表單form頁面。針對不同的設備(電腦,平板,手機)都可以單獨定制。
UI界面

頁面構建

運行時
代碼
說明
采用開源框架vuesortable,基於vue的實現排序,支持拖拽。頁面構建分為左中右三個部分,左邊為候選字段,中間為需要顯示的字段,右邊可以針對每個字段單獨設置一些屬性,比如寬度等。
數據表
創建表單tableFormBuilder,用於存儲頁面構建json數據,包括類型type、設備device、內容body等字段, 充分利用crudapi功能,API部分零代碼實現。

tableFormBuilder
核心代碼
頁面構建
<draggable
class="dragArea list-group row"
:list="selectedList"
group="people"
@change="log"
>
<div class="list-group-item q-pa-md"
v-for="formElement in selectedList"
:key="formElement.columnId"
:class="formElement | classFormat(currentElement)"
@click="selectForEdit(formElement)"
>
<div>
<div
v-bind:class="{ 'required': !formElement.column.nullable}">
{{formElement.column.caption}}:
</div>
<q-input v-if="isStringType(formElement)"
readonly
:placeholder="formElement.column.description"
:type="formElement.isPwd ? 'password' : 'text'"
v-model="formElement.column.value" >
<template v-slot:append v-if="!formElement.isText" >
<q-icon
:name="formElement.isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="formElement.isPwd = !formElement.isPwd"
/>
</template>
</q-input>
<q-editor readonly v-else-if="isTextType(formElement)"
v-model="textValue"
:placeholder="formElement.column.description" >
</q-editor>
<q-input v-else-if="isDateTimeType(formElement)" readonly>
<template v-slot:prepend>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
<q-date
mask="YYYY-MM-DD HH:mm:ss"
@input="hideRefPopProxyAction('qDateProxy')" />
</q-popup-proxy>
</q-icon>
</template>
<template v-slot:append>
<q-icon name="access_time" class="cursor-pointer">
<q-popup-proxy ref="qTimeProxy" transition-show="scale" transition-hide="scale">
<q-time mask="YYYY-MM-DD HH:mm:ss"
format24h with-seconds
@input="hideRefPopProxyAction('qTimeProxy')" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input v-else-if="isDateType(formElement)" readonly>
<template v-slot:append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
<q-date
mask="YYYY-MM-DD"
@input="hideRefPopProxyAction('qDateProxy')" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input v-else-if="isTimeType(formElement)" readonly>
<template v-slot:append>
<q-icon name="access_time" class="cursor-pointer">
<q-popup-proxy ref="qTimeProxy" transition-show="scale" transition-hide="scale">
<q-time mask="HH:mm:ss"
format24h with-seconds
@input="hideRefPopProxyAction('qTimeProxy')" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-toggle v-else-if="isBoolType(formElement)" readonly
v-model="formElement.column.value">
</q-toggle>
<q-input readonly
v-else-if="isNumberType(formElement)"
:placeholder="formElement.column.description"
type="number"
v-model="formElement.column.value" >
</q-input>
<CFile v-else-if="isAttachmentType(formElement)"
v-model="formElement.column.value" >
</CFile>
<q-input v-else
readonly
:placeholder="formElement.column.description"
:type="formElement.isPwd ? 'password' : 'text'"
v-model="formElement.column.value" >
<template v-slot:append v-if="!formElement.isText" >
<q-icon
:name="formElement.isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="formElement.isPwd = !formElement.isPwd"
/>
</template>
</q-input>
</div>
<div class="row reverse editable-element-action-buttons">
<div class="justify-end q-pt-xs">
<q-btn
@click="deleteElement(formElement)"
v-if="isSelectedForEdit(formElement)"
class="editable-element-button"
color="red"
icon="delete"
round unelevated size="xs">
<q-tooltip>移除</q-tooltip>
</q-btn>
</div>
</div>
</div>
</draggable>
通過draggable標簽實現
運行時渲染
<div v-if="selectedList.length > 0" class="row">
<div class="list-group-item q-pa-md"
v-for="formElement in selectedList"
:key="formElement.columnId"
:class="formElement | classFormat">
<div>
<div
v-bind:class="{ 'required': !formElement.column.nullable}">
{{formElement.column.caption}}:
</div>
<div class="row items-baseline content-center"
style="border-bottom: 1px solid rgba(0,0,0,0.12)"
v-if="formElement.column.relationTableName">
<div class="col-11">
<span>{{ formElement.column.value | relationDataFormat(formElement.column) }}</span>
</div>
<div class="col-1">
<q-btn round dense flat icon="zoom_in"
@click="openDialogClickAction(formElement.column)" />
</div>
</div>
<q-input v-else-if="isStringType(formElement.column.dataType)"
v-model="formElement.column.value"
:placeholder="formElement.column.description"
:type="formElement.isPwd ? 'password' : 'text'" >
<template v-slot:append v-if="!formElement.isText" >
<q-icon
:name="formElement.isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="formElement.isPwd = !formElement.isPwd"
/>
</template>
</q-input>
<q-editor v-else-if="isTextType(formElement.column.dataType)"
v-model="formElement.column.value"
:placeholder="formElement.column.description" >
</q-editor>
<q-input v-else-if="isDateTimeType(formElement.column.dataType)"
v-model="formElement.column.value" >
<template v-slot:prepend>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
<q-date v-model="formElement.column.value"
mask="YYYY-MM-DD HH:mm:ss"
@input="hideRefPopProxyAction('qDateProxy')" />
</q-popup-proxy>
</q-icon>
</template>
<template v-slot:append>
<q-icon name="access_time" class="cursor-pointer">
<q-popup-proxy ref="qTimeProxy" transition-show="scale" transition-hide="scale">
<q-time v-model="formElement.column.value"
mask="YYYY-MM-DD HH:mm:ss"
format24h with-seconds
@input="hideRefPopProxyAction('qTimeProxy')" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input v-else-if="isDateType(formElement.column.dataType)"
v-model="formElement.column.value">
<template v-slot:append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
<q-date v-model="formElement.column.value"
mask="YYYY-MM-DD"
@input="hideRefPopProxyAction('qDateProxy')" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input v-else-if="isTimeType(formElement.column.dataType)"
v-model="formElement.column.value" >
<template v-slot:append>
<q-icon name="access_time" class="cursor-pointer">
<q-popup-proxy ref="qTimeProxy" transition-show="scale" transition-hide="scale">
<q-time v-model="formElement.column.value"
mask="HH:mm:ss"
format24h with-seconds
@input="hideRefPopProxyAction('qTimeProxy')" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-toggle v-else-if="isBoolType(formElement.column.dataType)"
v-model="formElement.column.value" >
</q-toggle>
<q-input
v-else-if="isNumberType(formElement.column.dataType)"
v-model="formElement.column.value"
:placeholder="formElement.column.description"
type="number">
</q-input>
<CFile v-else-if="isAttachmentType(formElement.column.dataType)"
v-model="formElement.column.value"
@input="(data)=>{
formElement.column.value = data.url;
}"></CFile>
<q-input v-else
v-model="formElement.column.value"
:placeholder="formElement.column.description"
:type="formElement.isPwd ? 'password' : 'text'" >
<template v-slot:append v-if="!formElement.isText" >
<q-icon
:name="formElement.isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="formElement.isPwd = !formElement.isPwd"
/>
</template>
</q-input>
</div>
</div>
</div>
判斷是否存在定制頁面,如果存在動態渲染,否則采用默認頁面布局。
例子
以產品為例,配置好錄入頁面之后,運行時原來的默認錄入頁面用新的頁面代替,新的表單頁面和之前配置的表單頁面一致,功能不受影響,可以正常的錄入數據。
小結
本文主要通過拖拽方式實現表單定制功能,使用非常方便,零代碼定制表單錄入和編輯頁面,滿足了個性化需求,整個過程無需寫代碼。
crudapi簡介
crudapi是crud+api組合,表示增刪改查接口,是一款零代碼可配置的產品。使用crudapi可以告別枯燥無味的增刪改查代碼,讓您更加專注業務,節約大量成本,從而提高工作效率。crudapi的目標是讓處理數據變得更簡單,所有人都可以免費使用!無需編程,通過配置自動生成crud增刪改查RESTful API,提供后台UI管理業務數據。基於主流的開源框架,擁有自主知識產權,支持二次開發。
demo演示
crudapi屬於產品級的零代碼平台,不同於自動代碼生成器,不需要生成Controller、Service、Repository、Entity等業務代碼,程序運行起來就可以使用,真正0代碼,可以覆蓋基本的和業務無關的CRUD RESTful API。
官網地址:https://crudapi.cn
測試地址:https://demo.crudapi.cn/crudapi/login
附源碼地址
GitHub地址
https://github.com/crudapi/crudapi-admin-web
Gitee地址
https://gitee.com/crudapi/crudapi-admin-web
由於網絡原因,GitHub可能速度慢,改成訪問Gitee即可,代碼同步更新。
