免費(fèi)開(kāi)源」基于Vue和Quasar的前端SPA項(xiàng)目crudapi零代碼開(kāi)發(fā)平臺(tái)后臺(tái)管理系統(tǒng)實(shí)戰(zhàn)之拖拽表單定制(十六)

基于Vue和Quasar的前端SPA項(xiàng)目實(shí)戰(zhàn)之拖拽表單定制(十六)

回顧

通過(guò)前一篇文章 基于Vue和Quasar的前端SPA項(xiàng)目實(shí)戰(zhàn)之動(dòng)態(tài)表單(五)的介紹,實(shí)現(xiàn)了元數(shù)據(jù)中動(dòng)態(tài)表單設(shè)計(jì)功能姿染,支持常見(jiàn)的數(shù)據(jù)類型和索引苛蒲,然后實(shí)現(xiàn)了動(dòng)態(tài)表單的crud增刪改查功能,所有的表單頁(yè)面都是默認(rèn)的風(fēng)格蔫劣。本文主要介紹拖拽表單定制功能诡必,通過(guò)拖拽的方式定制表單錄入和編輯頁(yè)面脊框,滿足了個(gè)性化需求涤妒。

簡(jiǎn)介

針對(duì)元數(shù)據(jù)表的每個(gè)字段单雾,通過(guò)拖拽方式?jīng)Q定是否顯示或者隱藏,然后還可以配置顯示的寬度她紫。最終以json格式保存到后臺(tái)數(shù)據(jù)庫(kù)硅堆,運(yùn)行時(shí)根據(jù)配置動(dòng)態(tài)渲染錄入和編輯表單form頁(yè)面。針對(duì)不同的設(shè)備(電腦贿讹,平板渐逃,手機(jī))都可以單獨(dú)定制。

UI界面

formbuilder

頁(yè)面構(gòu)建

runtime

運(yùn)行時(shí)

代碼

說(shuō)明

采用開(kāi)源框架vuesortable围详,基于vue的實(shí)現(xiàn)排序朴乖,支持拖拽。頁(yè)面構(gòu)建分為左中右三個(gè)部分助赞,左邊為候選字段买羞,中間為需要顯示的字段,右邊可以針對(duì)每個(gè)字段單獨(dú)設(shè)置一些屬性雹食,比如寬度等畜普。

數(shù)據(jù)表

創(chuàng)建表單tableFormBuilder,用于存儲(chǔ)頁(yè)面構(gòu)建json數(shù)據(jù)群叶,包括類型type吃挑、設(shè)備device、內(nèi)容body等字段, 充分利用crudapi功能街立,API部分零代碼實(shí)現(xiàn)舶衬。

tableFormBuilder

tableFormBuilder

核心代碼

頁(yè)面構(gòu)建

<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>

通過(guò)draggable標(biāo)簽實(shí)現(xiàn)

運(yùn)行時(shí)渲染

<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>

判斷是否存在定制頁(yè)面,如果存在動(dòng)態(tài)渲染赎离,否則采用默認(rèn)頁(yè)面布局逛犹。

例子

以產(chǎn)品為例,配置好錄入頁(yè)面之后梁剔,運(yùn)行時(shí)原來(lái)的默認(rèn)錄入頁(yè)面用新的頁(yè)面代替虽画,新的表單頁(yè)面和之前配置的表單頁(yè)面一致,功能不受影響荣病,可以正常的錄入數(shù)據(jù)码撰。

小結(jié)

本文主要通過(guò)拖拽方式實(shí)現(xiàn)表單定制功能,使用非常方便个盆,零代碼定制表單錄入和編輯頁(yè)面脖岛,滿足了個(gè)性化需求,整個(gè)過(guò)程無(wú)需寫代碼砾省。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸡岗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子编兄,更是在濱河造成了極大的恐慌轩性,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狠鸳,死亡現(xiàn)場(chǎng)離奇詭異揣苏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)件舵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門卸察,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人铅祸,你說(shuō)我怎么就攤上這事坑质『衔洌” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵涡扼,是天一觀的道長(zhǎng)稼跳。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吃沪,這世上最難降的妖魔是什么汤善? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮票彪,結(jié)果婚禮上红淡,老公的妹妹穿的比我還像新娘。我一直安慰自己降铸,他們只是感情好在旱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著推掸,像睡著了一般颈渊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上终佛,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天俊嗽,我揣著相機(jī)與錄音,去河邊找鬼铃彰。 笑死绍豁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的牙捉。 我是一名探鬼主播竹揍,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼邪铲!你這毒婦竟也來(lái)了芬位?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤带到,失蹤者是張志新(化名)和其女友劉穎昧碉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體揽惹,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡被饿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搪搏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狭握。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疯溺,靈堂內(nèi)的尸體忽然破棺而出论颅,到底是詐尸還是另有隱情哎垦,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布恃疯,位于F島的核電站撼泛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏澡谭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一损俭、第九天 我趴在偏房一處隱蔽的房頂上張望蛙奖。 院中可真熱鬧,春花似錦杆兵、人聲如沸雁仲。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)攒砖。三九已至,卻和暖如春日裙,著一層夾襖步出監(jiān)牢的瞬間吹艇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工昂拂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留受神,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓格侯,卻偏偏與公主長(zhǎng)得像鼻听,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子联四,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容