| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688 |
- <template>
- <div class="form-builder">
- <div>
- <h2 class="template-title">{{ templateName }}</h2>
- </div>
- <el-row :gutter="20" class="main-container">
- <el-col :span="5" class="field-selector">
- <div class="panel-content">
- <h3 class="panel-title">选择组件</h3>
- <div class="field-list">
- <div
- v-for="(field, index) in availableFields"
- :key="field.componentId"
- class="field-item"
- @click="addFieldToForm(field, index)"
- >
- <el-button size="small">{{ field.label }}</el-button>
- </div>
- </div>
- </div>
- </el-col>
- <el-col :span="14" class="form-preview">
- <template v-if="formFields.length === 0">
- <div class="empty-tip">请选择添加左侧样式</div>
- </template>
- <draggable
- v-model="formFields"
- 200 animation:
- group="form-fields"
- @end="handleDragEnd"
- >
- <el-scrollbar style="height: 500px; width: 100%;">
- <div
- v-for="(field, index) in formFields"
- :key="index"
- :class="{ 'form-field-active': activeFieldIndex === index }"
- class="form-field-item"
- @click="setActiveField(index)"
- >
- <div class="field-header">
- <el-row align="middle" type="flex">
- <el-col :span="23" class="field-label">
- <span v-if="field.required" class="required-mark">*</span>
- {{ field.label }}
- </el-col>
- <el-col :span="1" class="field-operation">
- <i
- v-if="activeFieldIndex === index"
- class="el-icon-delete"
- @click.stop="removeFormField(index)"
- ></i>
- </el-col>
- </el-row>
- </div>
- <!-- 字段输入组件 -->
- <div class="field-input">
- <!-- 单选框 -->
- <template v-if="field.type === 'radio'">
- <el-radio-group v-model="field.value">
- <el-radio
- v-for="(option, optIdx) in field.options"
- :key="optIdx"
- :label="option"
- style="width: 90px"
- >
- {{ option }}
- </el-radio>
- </el-radio-group>
- </template>
- <!-- 下拉选择框 -->
- <template v-else-if="field.type === 'select'">
- <el-select
- v-if="!field.multiple"
- v-model="field.value"
- :multiple="field.multiple"
- :placeholder="`请选择${field.label}`"
- clearable
- size="small"
- style="width: 100%"
- >
- <el-option
- v-for="(option, optIdx) in field.options"
- :key="optIdx"
- :label="option"
- :value="optIdx"
- ></el-option>
- </el-select>
- <el-select
- v-if="field.multiple"
- v-model="field.multiples"
- :multiple="field.multiple"
- :placeholder="`请选择${field.label}`"
- clearable
- size="small"
- style="width: 100%"
- >
- <el-option
- v-for="(option, optIdx) in field.options"
- :key="optIdx"
- :label="option"
- :value="optIdx"
- ></el-option>
- </el-select>
- </template>
- <!-- 文本输入框 -->
- <template v-else-if="field.type === 'text'">
- <el-input
- v-model="field.value"
- :placeholder="field.placeholder || `请输入${field.label}`"
- clearable
- size="small"
- ></el-input>
- </template>
- <!-- 复选框 -->
- <template v-else-if="field.type === 'checkbox'">
- <div class="option-container">
- <el-checkbox-group v-model="field.multiples">
- <el-checkbox
- v-for="(option, optIdx) in field.options"
- :key="optIdx"
- :label="optIdx"
- style="width: 90px"
- >{{ option }}
- </el-checkbox>
- </el-checkbox-group>
- </div>
- </template>
- </div>
- </div>
- </el-scrollbar>
- </draggable>
- </el-col>
- <el-col :span="5" class="field-configurator">
- <template v-if="formFields.length > 0">
- <el-form class="config-form">
- <!-- 字段名称配置 -->
- <el-form-item label="字段名称">
- <el-input
- v-model="formFields[activeFieldIndex].label"
- clearable
- size="small"
- ></el-input>
- </el-form-item>
- <!-- <template v-if="formFields[activeFieldIndex].type !== 'radio' && formFields[activeFieldIndex].type !== 'checkbox'">-->
- <!-- <el-form-item label="默认值">-->
- <!-- <el-select-->
- <!-- v-model="defaultValueMode"-->
- <!-- size="small"-->
- <!-- style="width: 100%; margin-bottom: 8px"-->
- <!-- >-->
- <!-- <el-option-->
- <!-- v-for="mode in valueModes"-->
- <!-- :key="mode.value"-->
- <!-- :label="mode.label"-->
- <!-- :value="mode.value"-->
- <!-- ></el-option>-->
- <!-- </el-select>-->
- <!-- <el-input-->
- <!-- :v-model="formFields[activeFieldIndex].value"-->
- <!-- clearable-->
- <!-- placeholder="请输入默认值"-->
- <!-- size="small"-->
- <!-- ></el-input>-->
- <!-- </el-form-item>-->
- <!-- </template>-->
- <template v-if="['radio', 'select','checkbox'].includes(formFields[activeFieldIndex].type)">
- <el-form-item label="选项配置">
- <draggable
- v-model="formFields[activeFieldIndex].options"
- animation:50
- group="options"
- @end="handleOptionDragEnd"
- >
- <el-row
- v-for="(option, idx) in formFields[activeFieldIndex].options"
- :key="idx"
- class="option-item"
- >
- <el-col :span="2" class="option-drag">
- <span class="el-icon-s-operation"></span>
- </el-col>
- <el-col :span="18">
- <el-input
- v-model="formFields[activeFieldIndex].options[idx]"
- clearable
- size="small"
- ></el-input>
- </el-col>
- <el-col :span="2" class="option-delete">
- <i
- class="el-icon-delete"
- @click.stop="removeOption(idx)"
- ></i>
- </el-col>
- </el-row>
- </draggable>
- <el-button
- size="small"
- style="width: 100%; margin-top: 8px"
- @click="addNewOption"
- >
- <span class="el-icon-plus"></span> 添加选项
- </el-button>
- </el-form-item>
- </template>
- <!-- 通用配置:是否必填 -->
- <el-form-item class="required-config" label="是否必填">
- <el-switch v-model="formFields[activeFieldIndex].required"></el-switch>
- </el-form-item>
- <el-form-item v-if="'select' === formFields[activeFieldIndex].type" class="required-config"
- label="是否多选"
- >
- <el-switch v-model="formFields[activeFieldIndex].multiple"></el-switch>
- </el-form-item>
- <!-- 文本字段:长度限制 -->
- <el-form-item
- v-if="formFields[activeFieldIndex].type === 'text' && formFields[activeFieldIndex].required"
- class="length-limit"
- label="长度限制"
- >
- <el-input
- v-model.number="formFields[activeFieldIndex].minLength"
- autocomplete="off"
- clearable
- placeholder="最小"
- size="small"
- style="width: 40%; display: inline-block"
- ></el-input>
- <span style="margin: 0 4px">-</span>
- <el-input
- v-model.number="formFields[activeFieldIndex].maxLength"
- autocomplete="off"
- clearable
- placeholder="最大"
- size="small"
- style="width: 40%; display: inline-block"
- ></el-input>
- </el-form-item>
- </el-form>
- </template>
- </el-col>
- </el-row>
- <el-row class="action-bar">
- <el-button size="small" @click="resetForm">重置</el-button>
- <el-button size="small" type="primary" @click="submitForm">保存配置</el-button>
- </el-row>
- </div>
- </template>
- <script>
- import draggable from 'vuedraggable'
- import { getTemplateField, saveTemplate } from '@/api/his/physicalReportTemplateField'
- export default {
- props: {
- templateName: {
- type: String,
- default: '体检报告',
- required: true
- }
- },
- components: { draggable },
- data() {
- return {
- templateId: null,
- availableFields: [
- { componentId: '1', label: '输入框', type: 'text' },
- { componentId: '2', label: '单选按钮', type: 'radio' },
- { componentId: '3', label: '下拉选项', type: 'select' },
- { componentId: '4', label: '复选框', type: 'checkbox' }
- ],
- // 中间表单已添加的字段列表
- formFields: [],
- // 当前激活的字段索引
- activeFieldIndex: 0,
- // 右侧配置:默认值模式
- defaultValueMode: 1,
- // 临时存储:默认值模式(用于非单选/下拉字段)
- valueModes: [{ label: '自定义输入', value: 1 }]
- }
- },
- created() {
- },
- computed: {
- // 当前激活字段的类型
- currentFieldType() {
- return this.formFields[this.activeFieldIndex]?.type
- }
- },
- methods: {
- // 添加字段到表单
- addFieldToForm(field, index) {
- // 检查字段是否已添加(避免重复)
- // const isExist = this.formFields.some(item => item.id === field.id)
- // if (isExist) return
- // 根据字段类型初始化配置
- const fieldConfig = {
- componentId: field.componentId,
- label: field.label,
- type: field.type,
- required: false,
- value: '',
- multiple: false,
- multiples: [],
- placeholder: ''
- }
- // 文本字段
- if (field.type === 'text') {
- fieldConfig.minLength = null
- fieldConfig.maxLength = null
- }
- // 单选/下拉字段
- if (['radio', 'select', 'checkbox'].includes(field.type)) {
- fieldConfig.options = ['选项1', '选项2']
- }
- this.formFields.push(fieldConfig)
- this.activeFieldIndex = this.formFields.length - 1
- },
- // 删除表单中的字段
- removeFormField(index) {
- this.formFields.splice(index, 1)
- if (this.activeFieldIndex === index) {
- this.activeFieldIndex = Math.min(index, this.formFields.length - 1)
- }
- },
- // 添加新选项
- addNewOption() {
- const currentField = this.formFields[this.activeFieldIndex]
- const optionCount = currentField.options.length + 1
- currentField.options.push(`选项${optionCount}`)
- },
- // 删除选项
- removeOption(index) {
- this.formFields[this.activeFieldIndex].options.splice(index, 1)
- },
- // 设置当前激活的字段
- setActiveField(index) {
- this.activeFieldIndex = index
- },
- // 拖拽结束事件
- handleDragEnd() {
- console.log('字段排序已更新----')
- },
- // 拖拽结束事件
- handleOptionDragEnd() {
- console.log('选项排序已更新----')
- },
- // 表单验证
- validateForm() {
- if (this.formFields.length === 0) {
- this.$message.error('请至少添加一个字段')
- return false
- }
- for (let i = 0; i < this.formFields.length; i++) {
- const field = this.formFields[i]
- if (!field.label.trim()) {
- this.activeFieldIndex = i
- this.$message.error('字段名称不能为空')
- return false
- }
- // // 验证必填字段
- // if (field.required && field.type !== 'checkbox' && !field.multiple && !field.value && field.value !== 0 || field.required && field.type === 'checkbox' && field.options.length === 0) {
- // this.activeFieldIndex = i
- // this.$message.error(`“${field.label}”为必填项,请补充`)
- // return false
- // }
- //
- // // 验证文本字段长度限制
- // if (field.type === 'text' && field.required) {
- // const valueLength = (field.value || '').length
- // if (field.minLength !== null && valueLength < field.minLength) {
- // this.$message.error(`“${field.label}”长度不能小于${field.minLength}个字符`)
- // return false
- // }
- // if (field.maxLength !== null && valueLength > field.maxLength) {
- // this.$message.error(`“${field.label}”长度不能大于${field.maxLength}个字符`)
- // return false
- // }
- // }
- }
- return true
- },
- // 提交表单配置
- submitForm() {
- if (!this.validateForm()) return
- // 数据过滤
- const formData = this.formFields.map(field => {
- const multiple = field.multiple ? 1 : 0
- const required = field.required ? 1 : 0
- const {
- componentId,
- label,
- type,
- value,
- options,
- maxLength,
- minLength
- } = field
- const result = {
- componentId,
- label,
- type,
- required,
- value,
- maxLength,
- minLength,
- multiple
- }
- if (options) {
- result.options = options.join(',')
- }
- return result
- })
- saveTemplate({ templateId: this.templateId, templateFieldList: formData }).then(response => {
- if (response.code === 200) {
- this.$message.success('保存成功!')
- }
- this.getTemplateField(this.templateId)
- })
- },
- // 重置表单
- resetForm() {
- this.formFields = []
- this.activeFieldIndex = 0
- this.$message.info('已重置表单配置')
- },
- /**
- * 获取自定义数据列表
- * **/
- getTemplateField(templateId) {
- this.templateId = templateId
- getTemplateField(templateId).then(response => {
- if (response.data.length > 0) {
- //处理数据处理
- this.formFields = response.data.map(item => {
- const multiple = item.multiple === 1
- const required = item.required === 1
- const multiples = []
- const {
- componentId,
- label,
- type,
- value,
- options,
- maxLength,
- minLength
- } = item
- const result = {
- componentId,
- label,
- type,
- required,
- value,
- maxLength,
- minLength,
- multiple,
- multiples
- }
- if (options) {
- result.options = options.split(',').map(item => item.trim())
- }
- return result
- })
- this.activeFieldIndex = this.formFields.length - 1
- }
- })
- }
- }
- }
- </script>
- <style scoped>
- .form-builder {
- padding: 15px;
- background-color: #f5f7fa;
- min-height: calc(94vh - 30px);
- }
- .main-container {
- margin-bottom: 20px;
- }
- .field-selector {
- background-color: #fff;
- border-radius: 6px;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
- padding: 15px !important;
- }
- .panel-title {
- font-size: 15px;
- color: #1f2329;
- margin: 0 0 15px;
- padding-left: 4px;
- font-weight: 500;
- }
- .field-list {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
- .field-item {
- width: 100px;
- box-sizing: border-box;
- }
- .field-item .el-button {
- width: 100%;
- }
- /* 中间表单预览区 */
- .form-preview {
- padding: 0 10px !important;
- }
- .empty-tip {
- height: 60px;
- line-height: 60px;
- text-align: center;
- border: 1px dashed #dcdfe6;
- color: #8c8c8c;
- border-radius: 6px;
- margin-top: 10px;
- }
- .form-field-item {
- background-color: #fff;
- border: 1px solid #e5e6eb;
- border-radius: 6px;
- padding: 12px 15px;
- margin-bottom: 12px;
- transition: all 0.2s;
- }
- .form-field-item:hover {
- border-color: #c0c4cc;
- }
- .form-field-active {
- border-color: #409eff;
- background-color: #f0f7ff;
- }
- .field-header {
- margin-bottom: 10px;
- }
- .field-label {
- font-size: 14px;
- color: #1f2329;
- }
- .required-mark {
- color: #ff4d4f;
- margin-right: 4px;
- }
- .field-operation {
- color: #8c8c8c;
- cursor: pointer;
- }
- .field-operation:hover {
- color: #ff4d4f;
- }
- .field-configurator {
- background-color: #fff;
- border-radius: 6px;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
- padding: 15px !important;
- }
- .config-form .el-form-item {
- margin-bottom: 12px;
- }
- .config-form .el-form-item__label {
- font-size: 13px;
- color: #4e5969;
- padding: 0 0 6px;
- line-height: 1;
- width: 100%;
- text-align: left;
- }
- .option-item {
- display: flex;
- align-items: center;
- width: 100%;
- margin: 0 !important;
- padding: 4px 0;
- }
- .option-drag {
- color: #c9cdD4;
- }
- .option-delete {
- color: #8c8c8c;
- cursor: pointer;
- }
- .option-delete:hover {
- color: #ff4d4f;
- }
- .action-bar {
- text-align: center;
- padding: 10px 0;
- }
- .action-bar .el-button {
- margin: 0 5px;
- }
- .empty-tip {
- height: 60px;
- line-height: 60px;
- text-align: center;
- border: 1px dashed #dcdfe6;
- color: #8c8c8c;
- border-radius: 6px;
- margin-top: 10px;
- }
- @media (max-width: 1200px) {
- .field-selector,
- .field-configurator {
- padding: 10px !important;
- }
- }
- .option-container {
- display: flex;
- flex-wrap: wrap;
- align-items: flex-start;
- gap: 16px;
- }
- .template-title {
- text-align: center;
- margin: 0;
- line-height: 50px;
- }
- </style>
|