🧹 清理重复配置文件
- 删除根目录中重复的 NestJS 配置文件 - 删除 tsconfig.json, tsconfig.build.json, eslint.config.mjs, .prettierrc - 保留 wwjcloud-nest/ 目录中的完整配置 - 避免配置冲突,确保项目结构清晰
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-image v-for="(item,index) in props.data.handle_field_value" :src="img(item)" class="w-[70px] h-[70px]" :class="{ 'mr-[5px]' : (index + 1) < props.data.handle_field_value.length }" fit="contain" :preview-src-list="imgList" :zoom-rate="1.2" :max-scale="7"
|
||||
:min-scale="0.2" :initial-index="index" :hide-on-click-modal="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { img } from '@/utils/common'
|
||||
import { t } from "@/lang";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
const imgList = computed(() => {
|
||||
return props.data.handle_field_value.map((item: any) => {
|
||||
return img(item)
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="form-render">{{ props.data.render_value }}</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { t } from "@/lang";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-render {
|
||||
word-wrap: break-word; /* 长单词或长字符串自动换行 */
|
||||
word-break: break-word; /* 强制断词换行 */
|
||||
white-space: pre-wrap; /* 保持空格和换行 */
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item :label="t('地址格式')">
|
||||
<el-radio-group v-model="diyStore.editComponent.addressFormat" class="!block">
|
||||
<el-radio class="!block" label="province/city/district/address">{{ t('省/市/区/街道/详细地址') }}</el-radio>
|
||||
<el-radio class="!block" label="province/city/district/street">{{ t('省/市/区/街道(镇)') }}</el-radio>
|
||||
<el-radio class="!block" label="province/city/district">{{ t('省/市/区(县)') }}</el-radio>
|
||||
<el-radio class="!block" label="province/city">{{ t('省/市') }}</el-radio>
|
||||
<el-radio class="!block" label="province">{{ t('省') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item class="display-block">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('隐私保护') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>会自动将提交的个人信息做加密展示。</p>
|
||||
<p>适用于公开展示收集的数据且不暴露用户隐私。</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="diyStore.editComponent.field.privacyProtection" :disabled ="diyStore.editComponent.addressFormat != 'province/city/district/address'" />
|
||||
<div class="text-sm text-gray-400">{{ t('提交后自动隐藏地址,仅管理员可查看') }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref,watch } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
return res
|
||||
}
|
||||
watch(
|
||||
() => diyStore.editComponent.addressFormat,
|
||||
(newVal) => {
|
||||
if (newVal !== 'province/city/district/address') {
|
||||
diyStore.editComponent.field.privacyProtection = false
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item :label="t('style')">
|
||||
<el-radio-group v-model="diyStore.editComponent.style">
|
||||
<el-radio label="style-1">{{ t('defaultSources') }}</el-radio>
|
||||
<el-radio label="style-2">{{ t('listStyle') }}</el-radio>
|
||||
<el-radio label="style-3">{{ t('dropDownStyle') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('option')">
|
||||
<div ref="formCheckboxRef">
|
||||
<div v-for="(option, index) in diyStore.editComponent.options" :key="option.id" class="option-item flex items-center mb-[15px]">
|
||||
<el-input v-model="diyStore.editComponent.options[index].text" class="!w-[215px]" :placeholder="t('optionPlaceholder')" maxlength="30" clearable />
|
||||
<span v-if="diyStore.editComponent.options.length > 1" @click="removeOption(index)" class="cursor-pointer ml-[5px] nc-iconfont nc-icon-shanchu-yuangaizhiV6xx"></span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-primary cursor-pointer mr-[10px]" @click="addOption">{{ t('addSingleOption') }}</span>
|
||||
<el-popover :visible="visible" placement="bottom" :width="300">
|
||||
<p class="mb-[5px]">{{ t('addMultipleOption') }}</p>
|
||||
<p class="text-[#888] text-[12px] mb-[5px]">{{ t('addOptionTips') }}</p>
|
||||
<el-input v-model.trim="optionsValue" type="textarea" clearable maxlength="200" show-word-limit />
|
||||
<div class="mt-[10px] text-right">
|
||||
<el-button size="small" text @click="visible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button size="small" type="primary" @click="batchAddOptions">{{ t('confirm') }}</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<span class="text-primary cursor-pointer" @click="visible = true">{{ t('addMultipleOption') }}</span>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<!-- <el-form label-width="100px" class="px-[10px]">-->
|
||||
<!-- <el-form-item class="display-block">-->
|
||||
<!-- <template #label>-->
|
||||
<!-- <div class="flex items-center">-->
|
||||
<!-- <span class="mr-[3px]">{{ t('隐私保护') }}</span>-->
|
||||
<!-- <el-tooltip effect="light" placement="top">-->
|
||||
<!-- <template #content>-->
|
||||
<!-- <p>会自动将提交的个人信息做加密展示。</p>-->
|
||||
<!-- <p>适用于公开展示收集的数据且不暴露用户隐私。</p>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-icon>-->
|
||||
<!-- <QuestionFilled color="#999999" />-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-switch v-model="diyStore.editComponent.field.privacyProtection" />-->
|
||||
<!-- <div class="text-sm text-gray-400">{{ t('提交后自动隐藏内容,仅管理员可查看') }}</div>-->
|
||||
<!-- </el-form-item>-->
|
||||
|
||||
<!-- </el-form>-->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import Sortable from 'sortablejs'
|
||||
import { range } from 'lodash-es'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
let pass = true
|
||||
for (let i = 0; i < diyStore.value[index].options.length; i++) {
|
||||
if (!diyStore.value[index].options[i].text) {
|
||||
res.code = false
|
||||
res.message = t('optionPlaceholder')
|
||||
pass = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!pass) return res
|
||||
|
||||
const uniqueOptions = uniqueByKey(diyStore.value[index].options, 'text')
|
||||
if (uniqueOptions.length != diyStore.value[index].options.length) {
|
||||
res.code = false
|
||||
res.message = t('errorTipsOne')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
diyStore.editComponent.options.forEach((item: any) => {
|
||||
if (!item.id) item.id = diyStore.generateRandom()
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
const optionsValue = ref()
|
||||
const addOption = () => {
|
||||
diyStore.editComponent.options.push({
|
||||
id: diyStore.generateRandom(),
|
||||
text: '选项' + (diyStore.editComponent.options.length + 1)
|
||||
})
|
||||
}
|
||||
|
||||
const removeOption = (index: any) => {
|
||||
diyStore.editComponent.options.splice(index, 1)
|
||||
}
|
||||
|
||||
// 批量添加选项
|
||||
const batchAddOptions = () => {
|
||||
if (optionsValue.value.trim()) {
|
||||
const newOptions = optionsValue.value.split(',').map((option: any) => {
|
||||
return {
|
||||
id: diyStore.generateRandom(),
|
||||
text: option.trim()
|
||||
}
|
||||
}).filter((option: any) => option.text !== '')
|
||||
|
||||
// 去除重复的选项
|
||||
const uniqueNewOptions = uniqueByKey(newOptions, 'text')
|
||||
|
||||
// 过滤掉已存在的选项
|
||||
const filteredNewOptions = uniqueNewOptions.filter((newOption: any) =>
|
||||
!diyStore.editComponent.options.some((existingOption: any) => existingOption.text === newOption.text)
|
||||
)
|
||||
|
||||
// 如果有新的选项,添加到选项列表中
|
||||
if (filteredNewOptions.length > 0) {
|
||||
diyStore.editComponent.options.push(...filteredNewOptions)
|
||||
} else {
|
||||
ElMessage({
|
||||
message: t('errorTipsTwo'),
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
optionsValue.value = ''
|
||||
visible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 数组去重
|
||||
const uniqueByKey = (arr: any, key: any) => {
|
||||
const seen = new Set()
|
||||
return arr.filter((item: any) => {
|
||||
const serializedKey = JSON.stringify(item[key])
|
||||
return seen.has(serializedKey) ? false : seen.add(serializedKey)
|
||||
})
|
||||
}
|
||||
|
||||
const formCheckboxRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const sortable = Sortable.create(formCheckboxRef.value, {
|
||||
group: 'option-item',
|
||||
animation: 200,
|
||||
onEnd: event => {
|
||||
const temp = diyStore.editComponent.options[event.oldIndex!]
|
||||
diyStore.editComponent.options.splice(event.oldIndex!, 1)
|
||||
diyStore.editComponent.options.splice(event.newIndex!, 0, temp)
|
||||
sortable.sort(
|
||||
range(diyStore.editComponent.options.length).map(value => {
|
||||
return value.toString()
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item :label="t('dataFormat')">
|
||||
<el-radio-group v-model="diyStore.editComponent.dateFormat">
|
||||
<div class="flex flex-col">
|
||||
<el-radio label="YYYY年M月D日">{{ dateFormat.format1 }}</el-radio>
|
||||
<el-radio label="YYYY-MM-DD">{{ dateFormat.format2 }}</el-radio>
|
||||
<el-radio label="YYYY/MM/DD">{{ dateFormat.format3 }}</el-radio>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('startDate') }}</h3>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.start.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('defaultValue')">
|
||||
<el-switch v-model="diyStore.editComponent.start.defaultControl" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="diyStore.editComponent.start.defaultControl">
|
||||
<el-radio-group v-model="diyStore.editComponent.start.dateWay">
|
||||
<el-radio label="current">{{ t('currentDate') }}</el-radio>
|
||||
<el-radio label="diy">{{ t('diyDate') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="diyStore.editComponent.start.defaultControl && diyStore.editComponent.start.dateWay == 'diy'">
|
||||
<el-date-picker v-model="diyStore.editComponent.field.default.start.date" format="YYYY/MM/DD" value-format="YYYY-MM-DD" type="date" :placeholder="t('startDataPlaceholder')" @change="startDateChange" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('endDate') }}</h3>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.end.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('defaultValue')">
|
||||
<el-switch v-model="diyStore.editComponent.end.defaultControl" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="diyStore.editComponent.end.defaultControl">
|
||||
<el-radio-group v-model="diyStore.editComponent.end.dateWay">
|
||||
<el-radio label="current">{{ t('currentDate') }}</el-radio>
|
||||
<el-radio label="diy">{{ t('diyDate') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="diyStore.editComponent.end.defaultControl && diyStore.editComponent.end.dateWay == 'diy'">
|
||||
<el-date-picker :disabled-date="disabledEndDate"
|
||||
v-model="diyStore.editComponent.field.default.end.date" format="YYYY/MM/DD"
|
||||
value-format="YYYY-MM-DD" type="date" :placeholder="t('endDataPlaceholder')"
|
||||
@change="endDateChange" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('textStyle') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('textFontSize')">
|
||||
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="18" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('textFontWeight')">
|
||||
<el-radio-group v-model="diyStore.editComponent.fontWeight">
|
||||
<el-radio :label="'normal'">{{ t('fontWeightNormal') }}</el-radio>
|
||||
<el-radio :label="'bold'">{{ t('fontWeightBold') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('textColor')">
|
||||
<el-color-picker v-model="diyStore.editComponent.textColor" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { timeTurnTimeStamp } from '@/utils/common'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
let starTime = diyStore.value[index].field.default.start.date
|
||||
let endTime = diyStore.value[index].field.default.end.date
|
||||
|
||||
const today = new Date()
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
|
||||
if (diyStore.editComponent.start.dateWay == 'current') {
|
||||
starTime = today.toISOString().split('T')[0]
|
||||
}
|
||||
if (diyStore.editComponent.end.dateWay == 'current') {
|
||||
endTime = today.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
if (diyStore.editComponent.start.defaultControl && starTime == '' && diyStore.editComponent.end.dateWay == 'diy') {
|
||||
res.code = false
|
||||
res.message = t('startDataTips')
|
||||
return res
|
||||
}
|
||||
if (diyStore.editComponent.end.defaultControl && endTime == '' && diyStore.editComponent.end.dateWay == 'diy') {
|
||||
res.code = false
|
||||
res.message = t('endDataTips')
|
||||
return res
|
||||
}
|
||||
|
||||
if (diyStore.editComponent.start.defaultControl && diyStore.editComponent.end.defaultControl && timeTurnTimeStamp(starTime) > timeTurnTimeStamp(endTime)) {
|
||||
res.code = false
|
||||
res.message = t('startEndDataTips')
|
||||
return res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const dateFormat: any = reactive({
|
||||
format1: '',
|
||||
format2: '',
|
||||
format3: ''
|
||||
})
|
||||
|
||||
// 结束日期-禁止的日期
|
||||
const disabledEndDate = (time: Date) => {
|
||||
let cutoffDate = null
|
||||
let bool = false
|
||||
if (diyStore.editComponent.start && diyStore.editComponent.start.defaultControl) {
|
||||
if (diyStore.editComponent.start.dateWay == 'diy') {
|
||||
cutoffDate = new Date(diyStore.editComponent.field.default.start.date)
|
||||
} else {
|
||||
cutoffDate = new Date()
|
||||
}
|
||||
bool = time.getTime() < cutoffDate.getTime()
|
||||
}
|
||||
return bool
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const today = new Date()
|
||||
const endDate = new Date()
|
||||
endDate.setDate(endDate.getDate() + 7) // 设置日期为7天后的日期
|
||||
|
||||
if (diyStore.editComponent.field.default.start.timestamp) {
|
||||
diyStore.editComponent.field.default.start.date = today.toISOString().split('T')[0]
|
||||
diyStore.editComponent.field.default.start.timestamp = parseInt(today.getTime() / 1000)
|
||||
}
|
||||
|
||||
if (diyStore.editComponent.field.default.end.timestamp) {
|
||||
diyStore.editComponent.field.default.end.date = endDate.toISOString().split('T')[0]
|
||||
diyStore.editComponent.field.default.end.timestamp = parseInt(endDate.getTime() / 1000)
|
||||
}
|
||||
|
||||
const year = today.getFullYear()
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(today.getDate()).padStart(2, '0')
|
||||
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
dateFormat.format1 = `${year}年${month}月${day}日`
|
||||
dateFormat.format2 = `${year}-${month}-${day}`
|
||||
dateFormat.format3 = `${year}/${month}/${day}`
|
||||
dateFormat.format4 = `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
})
|
||||
|
||||
// 开始日期选择器
|
||||
const startDateChange = (date) => {
|
||||
diyStore.editComponent.field.default.start.date = date
|
||||
diyStore.editComponent.field.default.start.timestamp = timeTurnTimeStamp(date)
|
||||
|
||||
const endDate = new Date(date)
|
||||
endDate.setDate(endDate.getDate() + 7)
|
||||
diyStore.editComponent.field.default.end.date = endDate.toISOString().split('T')[0]
|
||||
diyStore.editComponent.field.default.end.timestamp = parseInt(endDate.getTime() / 1000)
|
||||
}
|
||||
// 结束日期选择器
|
||||
const endDateChange = (date) => {
|
||||
diyStore.editComponent.field.default.end.date = date
|
||||
diyStore.editComponent.field.default.end.timestamp = timeTurnTimeStamp(date)
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
101
admin-vben/src/app/views/diy_form/components/edit-form-date.vue
Normal file
101
admin-vben/src/app/views/diy_form/components/edit-form-date.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('dataFormat')">
|
||||
<el-radio-group v-model="diyStore.editComponent.dateFormat" class="!block">
|
||||
<el-radio class="!block" label="YYYY年M月D日">{{ dateFormat.format1 }}</el-radio>
|
||||
<el-radio class="!block" label="YYYY-MM-DD">{{ dateFormat.format2 }}</el-radio>
|
||||
<el-radio class="!block" label="YYYY/MM/DD">{{ dateFormat.format3 }}</el-radio>
|
||||
<el-radio class="!block" label="YYYY-MM-DD HH:mm">{{ dateFormat.format4 }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('defaultValue')">
|
||||
<el-switch v-model="diyStore.editComponent.defaultControl" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="diyStore.editComponent.defaultControl">
|
||||
<el-radio-group v-model="diyStore.editComponent.dateWay">
|
||||
<el-radio label="current">{{ t('currentDate') }}</el-radio>
|
||||
<el-radio label="diy">{{ t('diyDate') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="diyStore.editComponent.defaultControl && diyStore.editComponent.dateWay == 'diy'">
|
||||
<el-date-picker v-if="diyStore.editComponent.dateFormat != 'YYYY-MM-DD HH:mm'" v-model="diyStore.editComponent.field.default.date" format="YYYY/MM/DD" value-format="YYYY-MM-DD" type="date" :placeholder="t('dataPlaceholder')" @change="dateChange" />
|
||||
<el-date-picker v-else v-model="diyStore.editComponent.field.default.date" format="YYYY/MM/DD HH:mm" value-format="YYYY-MM-DD HH:mm" type="datetime" :placeholder="t('dataPlaceholder')" @change="dateChange" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { timeTurnTimeStamp } from '@/utils/common'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
return res
|
||||
}
|
||||
|
||||
const dateFormat: any = reactive({
|
||||
format1: '',
|
||||
format2: '',
|
||||
format3: '',
|
||||
format4: ''
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 初始赋值当天日期
|
||||
const today = new Date()
|
||||
if (!diyStore.editComponent.field.default.date) {
|
||||
diyStore.editComponent.field.default.date = today.toISOString().split('T')[0]
|
||||
diyStore.editComponent.field.default.timestamp = today.getTime() / 1000
|
||||
}
|
||||
const year = today.getFullYear()
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(today.getDate()).padStart(2, '0')
|
||||
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
dateFormat.format1 = `${year}年${month}月${day}日`
|
||||
dateFormat.format2 = `${year}-${month}-${day}`
|
||||
dateFormat.format3 = `${year}/${month}/${day}`
|
||||
dateFormat.format4 = `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
})
|
||||
|
||||
const dateChange = (date: any) => {
|
||||
diyStore.editComponent.field.default.date = date
|
||||
diyStore.editComponent.field.default.timestamp = timeTurnTimeStamp(date)
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
// todo 只需要考虑该组件自身的验证
|
||||
return res
|
||||
}
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('限制上传大小')">
|
||||
<el-input v-model.trim="diyStore.editComponent.limitUploadSize" clearable maxlength="15" />
|
||||
/单位MB,目前是Bit,要转换,*1024
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('preventDuplication') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>{{ t('preventDuplicationTipsOne') }}</p>
|
||||
<p>{{ t('preventDuplicationTipsTwo') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="diyStore.editComponent.field.unique" />
|
||||
</el-form-item>
|
||||
<el-form-item class="display-block">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('privacyProtection') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>{{ t('privacyProtectionTipsOne') }}</p>
|
||||
<p>{{ t('privacyProtectionTipsTwo') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="diyStore.editComponent.field.privacyProtection" />
|
||||
<div class="text-sm text-gray-400">{{ t('privacyProtectionTipsThree') }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
// todo 只需要考虑该组件自身的验证
|
||||
return res
|
||||
}
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('imageLimit')">
|
||||
<el-input v-model.trim="diyStore.editComponent.limit" :placeholder="t('imageLimitPlaceholder')" clearable maxlength="2" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('上传方式')">
|
||||
<el-checkbox-group v-model="diyStore.editComponent.uploadMode" :min="1">
|
||||
<el-checkbox label="拍照上传" value="take_pictures" />
|
||||
<el-checkbox label="从相册选择" value="select_from_album" />
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
if (diyStore.value[index].limit == '') {
|
||||
res.code = false
|
||||
res.message = t('imageLimitPlaceholder')
|
||||
return res
|
||||
}
|
||||
if (isNaN(diyStore.value[index].limit) || !regExp.number.test(diyStore.value[index].limit)) {
|
||||
res.code = false
|
||||
res.message = t('imageLimitErrorTips')
|
||||
return res
|
||||
}
|
||||
if (diyStore.value[index].limit < 0) {
|
||||
res.code = false
|
||||
res.message = t('imageLimitErrorTipsTwo')
|
||||
return res
|
||||
}
|
||||
if (diyStore.value[index].limit == 0) {
|
||||
res.code = false
|
||||
res.message = t('imageLimitErrorTipsThree')
|
||||
return res
|
||||
}
|
||||
if (diyStore.value[index].limit > 9) {
|
||||
res.code = false
|
||||
res.message = t('imageLimitErrorTipsFour')
|
||||
return res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 正则表达式
|
||||
const regExp: any = {
|
||||
required: /[\S]+/,
|
||||
number: /^\d{0,10}$/,
|
||||
digit: /^\d{0,10}(.?\d{0,2})$/,
|
||||
special: /^\d{0,10}(.?\d{0,3})$/
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('defaultValue') }}</span>
|
||||
<el-tooltip effect="light" :content="t('defaultValueTips')" placement="top">
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model.trim="diyStore.editComponent.field.default" :placeholder="t('defaultValuePlaceholder')" clearable maxlength="18" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('preventDuplication') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>{{ t('preventDuplicationTipsOne') }}</p>
|
||||
<p>{{ t('preventDuplicationTipsTwo') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="diyStore.editComponent.field.unique" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item class="display-block">-->
|
||||
<!-- <template #label>-->
|
||||
<!-- <div class="flex items-center">-->
|
||||
<!-- <span class="mr-[3px]">{{ t('隐私保护') }}</span>-->
|
||||
<!-- <el-tooltip effect="light" placement="top">-->
|
||||
<!-- <template #content>-->
|
||||
<!-- <p>会自动将提交的个人信息做加密展示。</p>-->
|
||||
<!-- <p>适用于公开展示收集的数据且不暴露用户隐私。</p>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-icon>-->
|
||||
<!-- <QuestionFilled color="#999999" />-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-switch v-model="diyStore.editComponent.field.privacyProtection" />-->
|
||||
<!-- <div class="text-sm text-gray-400">{{ t('提交后自动隐藏文本,仅管理员可查看') }}</div>-->
|
||||
<!-- </el-form-item>-->
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
// todo 只需要考虑该组件自身的验证
|
||||
return res
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item :label="t('access')">
|
||||
<el-radio-group v-model="diyStore.editComponent.mode">
|
||||
<el-radio class="!mr-[20px]" label="authorized_wechat_location">{{ t('authorizeWeChatLocation') }}</el-radio>
|
||||
<el-radio label="open_choose_location">{{ t('manuallySelectPositioning') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item class="display-block">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('privacyProtection') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>{{ t('privacyProtectionTipsOne') }}</p>
|
||||
<p>{{ t('privacyProtectionTipsTwo') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="diyStore.editComponent.field.privacyProtection" />
|
||||
<div class="text-sm text-gray-400">{{ t('privacyProtectionTipsFour') }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
// todo 只需要考虑该组件自身的验证
|
||||
return res
|
||||
}
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('preventDuplication') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>{{ t('preventDuplicationTipsOne') }}</p>
|
||||
<p>{{ t('preventDuplicationTipsTwo') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="diyStore.editComponent.field.unique" />
|
||||
</el-form-item>
|
||||
<el-form-item class="display-block">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('privacyProtection') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>{{ t('privacyProtectionTipsOne') }}</p>
|
||||
<p>{{ t('privacyProtectionTipsTwo') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="diyStore.editComponent.field.privacyProtection" />
|
||||
<div class="text-sm text-gray-400">{{ t('privacyProtectionTipsFive') }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
// todo 只需要考虑该组件自身的验证
|
||||
return res
|
||||
}
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('unit')">
|
||||
<el-input v-model.trim="diyStore.editComponent.unit" :placeholder="t('unitPlaceholder')" clearable maxlength="5" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('defaultValue') }}</span>
|
||||
<el-tooltip effect="light" :content="t('defaultValueTips')" placement="top">
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model.trim="diyStore.editComponent.field.default" :placeholder="t('defaultValuePlaceholder')" @keyup="filterDigit($event)" clearable maxlength="18" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<!-- <el-form-item>-->
|
||||
<!-- <template #label>-->
|
||||
<!-- <div class="flex items-center">-->
|
||||
<!-- <span class="mr-[3px]">{{ t('内容防重复') }}</span>-->
|
||||
<!-- <el-tooltip effect="light" placement="top">-->
|
||||
<!-- <template #content>-->
|
||||
<!-- <p>该组件填写的内容不能与已提交的数据重复。</p>-->
|
||||
<!-- <p>极端情况下可能存在延时导致限制失效。</p>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-icon>-->
|
||||
<!-- <QuestionFilled color="#999999" />-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-switch v-model="diyStore.editComponent.field.unique" />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item class="display-block">-->
|
||||
<!-- <template #label>-->
|
||||
<!-- <div class="flex items-center">-->
|
||||
<!-- <span class="mr-[3px]">{{ t('隐私保护') }}</span>-->
|
||||
<!-- <el-tooltip effect="light" placement="top">-->
|
||||
<!-- <template #content>-->
|
||||
<!-- <p>会自动将提交的个人信息做加密展示。</p>-->
|
||||
<!-- <p>适用于公开展示收集的数据且不暴露用户隐私。</p>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-icon>-->
|
||||
<!-- <QuestionFilled color="#999999" />-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-switch v-model="diyStore.editComponent.field.privacyProtection" />-->
|
||||
<!-- <div class="text-sm text-gray-400">{{ t('提交后自动隐藏数字,仅管理员可查看') }}</div>-->
|
||||
<!-- </el-form-item>-->
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import { filterDigit } from '@/utils/common'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
if (diyStore.value[index].field.default) {
|
||||
if (isNaN(diyStore.value[index].field.default) || !regExp.digit.test(diyStore.value[index].field.default)) {
|
||||
res.code = false
|
||||
res.message = t('defaultErrorTips')
|
||||
} else if (diyStore.value[index].field.default < 0) {
|
||||
res.code = false
|
||||
res.message = t('defaultMustZeroTips')
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 正则表达式
|
||||
const regExp: any = {
|
||||
required: /[\S]+/,
|
||||
number: /^\d{0,10}$/,
|
||||
digit: /^\d{0,10}(.?\d{0,2})$/,
|
||||
special: /^\d{0,10}(.?\d{0,3})$/
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
214
admin-vben/src/app/views/diy_form/components/edit-form-radio.vue
Normal file
214
admin-vben/src/app/views/diy_form/components/edit-form-radio.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item :label="t('style')">
|
||||
<el-radio-group v-model="diyStore.editComponent.style">
|
||||
<el-radio label="style-1">{{ t('defaultSources') }}</el-radio>
|
||||
<el-radio label="style-2">{{ t('listStyle') }}</el-radio>
|
||||
<el-radio label="style-3">{{ t('dropDownStyle') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('option')">
|
||||
<div ref="formRadioRef">
|
||||
<div v-for="(option, index) in diyStore.editComponent.options" :key="option.id" class="option-item flex items-center mb-[15px]">
|
||||
<el-input v-model="diyStore.editComponent.options[index].text" class="!w-[215px]" :placeholder="t('optionPlaceholder')" clearable maxlength="30" />
|
||||
<span v-if="diyStore.editComponent.options.length > 1" @click="removeOption(index)" class="cursor-pointer ml-[5px] nc-iconfont nc-icon-shanchu-yuangaizhiV6xx"></span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-primary cursor-pointer mr-[10px]" @click="addOption">{{ t('addSingleOption') }}</span>
|
||||
<el-popover :visible="visible" placement="bottom" :width="300">
|
||||
<p class="mb-[5px]">{{ t('addMultipleOption') }}</p>
|
||||
<p class="text-[#888] text-[12px] mb-[5px]">{{ t('addOptionTips') }}</p>
|
||||
<el-input v-model.trim="optionsValue" type="textarea" clearable maxlength="200" show-word-limit />
|
||||
<div class="mt-[10px] text-right">
|
||||
<el-button size="small" text @click="visible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button size="small" type="primary" @click="batchAddOptions">{{ t('confirm') }}</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<span class="text-primary cursor-pointer" @click="visible = true">{{ t('addMultipleOption') }}</span>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item class="display-block">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('逻辑规则') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>支持选择某个选项后,显示特定的组件。</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-button plain>{{ t('添加字段显示规则') }}</el-button>
|
||||
<span class="mr-[3px]">1条字段显示规则</span>
|
||||
<span class="text-primary cursor-pointer" @click="">设置</span>
|
||||
</div>
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<!-- <el-form label-width="100px" class="px-[10px]">-->
|
||||
<!-- <el-form-item class="display-block">-->
|
||||
<!-- <template #label>-->
|
||||
<!-- <div class="flex items-center">-->
|
||||
<!-- <span class="mr-[3px]">{{ t('隐私保护') }}</span>-->
|
||||
<!-- <el-tooltip effect="light" placement="top">-->
|
||||
<!-- <template #content>-->
|
||||
<!-- <p>会自动将提交的个人信息做加密展示。</p>-->
|
||||
<!-- <p>适用于公开展示收集的数据且不暴露用户隐私。</p>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-icon>-->
|
||||
<!-- <QuestionFilled color="#999999" />-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-switch v-model="diyStore.editComponent.field.privacyProtection" />-->
|
||||
<!-- <div class="text-sm text-gray-400">{{ t('提交后自动隐藏内容,仅管理员可查看') }}</div>-->
|
||||
<!-- </el-form-item>-->
|
||||
|
||||
<!-- </el-form>-->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import Sortable from 'sortablejs'
|
||||
import { range } from 'lodash-es'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
let pass = true
|
||||
for (let i = 0; i < diyStore.value[index].options.length; i++) {
|
||||
if (!diyStore.value[index].options[i].text) {
|
||||
res.code = false
|
||||
res.message = t('optionPlaceholder')
|
||||
pass = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!pass) return res
|
||||
|
||||
const uniqueOptions = uniqueByKey(diyStore.value[index].options, 'text')
|
||||
|
||||
if (uniqueOptions.length != diyStore.value[index].options.length) {
|
||||
res.code = false
|
||||
res.message = t('errorTipsOne')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
diyStore.editComponent.options.forEach((item: any) => {
|
||||
if (!item.id) item.id = diyStore.generateRandom()
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
const optionsValue = ref()
|
||||
const addOption = () => {
|
||||
diyStore.editComponent.options.push({
|
||||
id: diyStore.generateRandom(),
|
||||
text: '选项' + (diyStore.editComponent.options.length + 1)
|
||||
})
|
||||
}
|
||||
|
||||
const removeOption = (index: any) => {
|
||||
diyStore.editComponent.options.splice(index, 1)
|
||||
}
|
||||
|
||||
const batchAddOptions = () => {
|
||||
if (optionsValue.value.trim()) {
|
||||
const newOptions = optionsValue.value.split(',').map((option: any) => {
|
||||
return {
|
||||
id: diyStore.generateRandom(),
|
||||
text: option.trim()
|
||||
}
|
||||
}).filter((option: any) => option.text !== '')
|
||||
|
||||
// 去除重复的选项
|
||||
const uniqueNewOptions = uniqueByKey(newOptions, 'text')
|
||||
|
||||
// 过滤掉已存在的选项
|
||||
const filteredNewOptions = uniqueNewOptions.filter(newOption =>
|
||||
!diyStore.editComponent.options.some(existingOption => existingOption.text === newOption.text)
|
||||
)
|
||||
|
||||
// 如果有新的选项,添加到选项列表中
|
||||
if (filteredNewOptions.length > 0) {
|
||||
diyStore.editComponent.options.push(...filteredNewOptions)
|
||||
} else {
|
||||
ElMessage({
|
||||
message: t('errorTipsTwo'),
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
|
||||
optionsValue.value = ''
|
||||
visible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 数组去重
|
||||
const uniqueByKey = (arr: any, key: any) => {
|
||||
const seen = new Set()
|
||||
return arr.filter((item: any) => {
|
||||
const serializedKey = JSON.stringify(item[key])
|
||||
return seen.has(serializedKey) ? false : seen.add(serializedKey)
|
||||
})
|
||||
}
|
||||
|
||||
const formRadioRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const sortable = Sortable.create(formRadioRef.value, {
|
||||
group: 'option-item',
|
||||
animation: 200,
|
||||
onEnd: event => {
|
||||
const temp = diyStore.editComponent.options[event.oldIndex!]
|
||||
diyStore.editComponent.options.splice(event.oldIndex!, 1)
|
||||
diyStore.editComponent.options.splice(event.newIndex!, 0, temp)
|
||||
sortable.sort(
|
||||
range(diyStore.editComponent.options.length).map(value => {
|
||||
return value.toString()
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
<div class="edit-attr-item-wrap">
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('floatBtnButton')" class="display-block">
|
||||
<el-radio-group v-model="diyStore.editComponent.btnPosition" @change="btnPositionChangeFn">
|
||||
<el-radio label="follow_content">{{ t('followContent') }}</el-radio>
|
||||
<el-radio label="hover_screen_bottom">{{ t('hoverScreenBottom') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="text-sm text-gray-400 mb-[5px] leading-[1.4]"
|
||||
v-show="diyStore.editComponent.btnPosition == 'follow_content'">{{ t('btnTips') }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-400 mb-[5px]"
|
||||
v-show="diyStore.editComponent.btnPosition == 'hover_screen_bottom'">{{ t('btnTipsTwo') }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-400 mb-[10px] leading-[1.4]">{{ t('btnTipsThree') }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('submitBtn') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('submitBtnName')">
|
||||
<el-input v-model.trim="diyStore.editComponent.submitBtn.text" :placeholder="t('btnNamePlaceholder')" clearable maxlength="10" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('textColor')">
|
||||
<el-color-picker v-model="diyStore.editComponent.submitBtn.color" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('subTextBgColor')">
|
||||
<el-color-picker v-model="diyStore.editComponent.submitBtn.bgColor" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('resetBtn') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('carouselSearchTabControl')">
|
||||
<el-switch v-model="diyStore.editComponent.resetBtn.control" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('submitBtnName')">
|
||||
<el-input v-model.trim="diyStore.editComponent.resetBtn.text" :placeholder="t('btnNamePlaceholder')" clearable maxlength="10" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('textColor')">
|
||||
<el-color-picker v-model="diyStore.editComponent.resetBtn.color" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('subTextBgColor')">
|
||||
<el-color-picker v-model="diyStore.editComponent.resetBtn.bgColor" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('btnStyle') }}</h3>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item :label="t('topRounded')">
|
||||
<el-slider v-model="diyStore.editComponent.topElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('bottomRounded')">
|
||||
<el-slider v-model="diyStore.editComponent.bottomElementRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="50" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 单选
|
||||
const btnPositionChangeFn = (e) => {
|
||||
if (e == 'hover_screen_bottom') {
|
||||
diyStore.editComponent.margin.bottom = 0
|
||||
diyStore.editComponent.margin.both = 0
|
||||
diyStore.editComponent.margin.top = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
if (diyStore.value[index].submitBtn.text == '') {
|
||||
res.code = false
|
||||
res.message = t('submitBtnNamePlaceholder')
|
||||
return res
|
||||
}
|
||||
if (diyStore.value[index].resetBtn.text == '') {
|
||||
res.code = false
|
||||
res.message = t('resetBtnNamePlaceholder')
|
||||
return res
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
405
admin-vben/src/app/views/diy_form/components/edit-form-table.vue
Normal file
405
admin-vben/src/app/views/diy_form/components/edit-form-table.vue
Normal file
@@ -0,0 +1,405 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('列设置')">
|
||||
<div ref="imageBoxRef">
|
||||
<div v-for="(item, index) in diyStore.editComponent.columnList" :key="item.id"
|
||||
class="border-b-[1px] border-[#e0e0e0] py-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex">
|
||||
<span :class="['iconfont', 'ml-[5px]', 'cursor-pointer', getIconClass(item.type)]"></span>
|
||||
<el-input v-model="item.name" class="input-style" :input-style="{ boxShadow: 'none' }"
|
||||
:placeholder="t('请输入列名')" />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span v-if="diyStore.editComponent.columnList.length > 1" @click="removeOption(index)"
|
||||
class="cursor-pointer ml-[5px] nc-iconfont nc-icon-shanchu-yuangaizhiV6xx"></span>
|
||||
<span class="cursor-pointer ml-[5px] nc-iconfont nc-icon-xiaV6xx"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.type == 'radio'" class="flex">
|
||||
<div class="text-[#999] mr-3" >{{ item.options?.length || 0 }}个选项</div>
|
||||
<span class="text-primary cursor-pointer mr-[10px]" @click="openRadioDialog(item, index)">{{ t('编辑') }}</span>
|
||||
</div>
|
||||
<div v-if="item.type == 'date'" class="flex">
|
||||
<span class="text-primary cursor-pointer mr-[10px]" @click="openRadioDialog(item, index)">{{ t('设置日期格式') }}</span>
|
||||
</div>
|
||||
<div v-if="item.type == 'address'" class="flex">
|
||||
<div class="text-[#999] mr-3">精确到详细地址</div>
|
||||
<span class="text-primary cursor-pointer mr-[10px]" @click="openRadioDialog(item, index)">{{ t('设置') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-popover placement="bottom" :width="50" trigger="hover">
|
||||
<template #reference>
|
||||
<span class="text-primary cursor-pointer mr-[10px]">{{ t('添加') }}</span>
|
||||
</template>
|
||||
<div v-for="(item, index) in columnTypeOptions" :key="index" @click="addOption(item)"
|
||||
class="cursor-pointer hover:bg-[#d1e1ff] rounded text-center">
|
||||
<div class="py-1 text-[var(--el-text-color-primary]">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('是否自增')">
|
||||
<el-switch v-model="diyStore.editComponent.autoIncrementControl" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('填写限制')" v-if="diyStore.editComponent.autoIncrementControl">
|
||||
<div class="flex items-center">
|
||||
<span>默认显示</span>
|
||||
<el-input v-model="diyStore.editComponent.writeLimit.default" class="input-short" :placeholder="t('')" />
|
||||
<span>项</span>
|
||||
</div>
|
||||
<div class="flex items-center my-1">
|
||||
<span>最少填写</span>
|
||||
<el-input v-model="diyStore.editComponent.writeLimit.min" class="input-short" :placeholder="t('')" />
|
||||
<span>项</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span>最多填写</span>
|
||||
<el-input v-model="diyStore.editComponent.writeLimit.max" class="input-short" :placeholder="t('')" />
|
||||
<span>项</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('按钮名称')" v-if="diyStore.editComponent.autoIncrementControl">
|
||||
<el-input v-model="diyStore.editComponent.btnText" :placeholder="t('请输入按钮名称')" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 单选项 -->
|
||||
<!-- <el-dialog v-model="radioDialogVisible" :title="t('设置单选项')" width="500">
|
||||
<div v-if="activeColumnTemp.type == 'radio'">
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('选项名称')">
|
||||
<el-input v-model="activeColumnTemp.name" :input-style="{ boxShadow: 'none' }" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('设置选项')">
|
||||
<div ref="radioBoxRef">
|
||||
<div v-for="(opt, idx) in activeColumnTemp.options" :key="opt.id">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex-1">
|
||||
<el-input v-model="opt.label" :input-style="{ boxShadow: 'none' }"
|
||||
:placeholder="t('请输入')" />
|
||||
</div>
|
||||
<span v-if="activeColumnTemp.options.length > 1" @click="removeOptionItem(idx)"
|
||||
class="cursor-pointer ml-[5px] nc-iconfont nc-icon-shanchu-yuangaizhiV6xx"></span>
|
||||
<span class="cursor-pointer ml-[5px] nc-iconfont nc-icon-iconpaixu1"></span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-primary cursor-pointer mr-[10px]" @click="addOptionItem">{{ t('添加选项') }}</span>
|
||||
<span class="text-primary cursor-pointer mr-[10px]" @click="addOtherOption">{{ t('添加其它项') }}</span>
|
||||
<el-popover :visible="visible" placement="bottom" :width="300">
|
||||
<p class="mb-[5px]">{{ t('addMultipleOption') }}</p>
|
||||
<p class="text-[#888] text-[12px] mb-[5px]">{{ t('addOptionTips') }}</p>
|
||||
<el-input v-model.trim="optionsValue" type="textarea" clearable maxlength="200"
|
||||
show-word-limit />
|
||||
<div class="mt-[10px] text-right">
|
||||
<el-button size="small" text @click="visible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button size="small" type="primary" @click="batchAddOptions">{{t('confirm')}}</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<span class="text-primary cursor-pointer"
|
||||
@click="visible = true">{{ t('addMultipleOption') }}</span>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div v-else-if="activeColumnTemp.type == 'date'">
|
||||
<el-form>
|
||||
<el-form-item :label="t('dataFormat')">
|
||||
<el-radio-group v-model="activeColumnTemp.dateFormat" class="!block">
|
||||
<el-radio class="!block" label="YYYY年M月D日">{{ dateFormat.format1 }}</el-radio>
|
||||
<el-radio class="!block" label="YYYY-MM-DD">{{ dateFormat.format2 }}</el-radio>
|
||||
<el-radio class="!block" label="YYYY/MM/DD">{{ dateFormat.format3 }}</el-radio>
|
||||
<el-radio class="!block" label="YYYY-MM-DD HH:mm">{{ dateFormat.format4 }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div v-else-if="activeColumnTemp.type == 'address'">
|
||||
<el-form>
|
||||
<el-form-item :label="t('地址格式')">
|
||||
<el-radio-group v-model="activeColumnTemp.addressFormat" class="!block">
|
||||
<el-radio class="!block" label="province/city/district/address">{{ t('省/市/区/街道/详细地址') }}</el-radio>
|
||||
<el-radio class="!block" label="province/city/district/street">{{ t('省/市/区/街道(镇)') }}</el-radio>
|
||||
<el-radio class="!block" label="province/city/district">{{ t('省/市/区(县)') }}</el-radio>
|
||||
<el-radio class="!block" label="province/city">{{ t('省/市') }}</el-radio>
|
||||
<el-radio class="!block" label="province">{{ t('省') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="radioDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleDialogConfirm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog> -->
|
||||
|
||||
<div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import Sortable from 'sortablejs'
|
||||
import { ref, watch, onMounted, nextTick, reactive, computed } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import { range } from 'lodash-es'
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
// todo 只需要考虑该组件自身的验证
|
||||
return res
|
||||
}
|
||||
|
||||
// 类型选项数组
|
||||
const columnTypeOptions = ref([
|
||||
{ label: '单选项', value: 'radio' },
|
||||
{ label: '文本', value: 'text' },
|
||||
{ label: '数字', value: 'number' },
|
||||
{ label: '手机号', value: 'mobile' },
|
||||
{ label: '地址', value: 'address' },
|
||||
{ label: '身份证', value: 'idcard' },
|
||||
{ label: '性别', value: 'gender' },
|
||||
{ label: '日期', value: 'date' }
|
||||
])
|
||||
|
||||
const getIconClass = (type:any) => {
|
||||
switch (type) {
|
||||
case 'radio':
|
||||
return 'icona-duihaopc30'
|
||||
case 'text':
|
||||
return 'icona-danhangwenben-1pc30'
|
||||
case 'number':
|
||||
return 'icona-shuzipc30-1'
|
||||
case 'mobile':
|
||||
return 'icona-shoujipc30'
|
||||
case 'address':
|
||||
return 'iconbiaotipc'
|
||||
case 'idcard':
|
||||
return 'icona-shenfenzhengpc30'
|
||||
case 'gender':
|
||||
return 'el-icon-s-opportunity'
|
||||
case 'date':
|
||||
return 'icona-riqipc30'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const imageBoxRef = ref()
|
||||
const generateId = () => Date.now().toString(36) + Math.random().toString(36).substr(2, 5)
|
||||
// 添加列方法
|
||||
const addOption = (item) => {
|
||||
const newColumn: any = {
|
||||
id: generateId(),
|
||||
name: item.label,
|
||||
type: item.value, // 列类型
|
||||
value: '' // 默认值(可选)
|
||||
}
|
||||
|
||||
// 如果是单选项,初始化 options
|
||||
if (item.value === 'radio') {
|
||||
newColumn.options = [
|
||||
{ id: generateId(), label: '选项1' },
|
||||
{ id: generateId(), label: '选项2' }
|
||||
]
|
||||
}
|
||||
// 如果是日期,初始化 dateFormat
|
||||
if (item.value === 'date') {
|
||||
newColumn.dateFormat = 'YYYY年M月D日' // 默认日期格式
|
||||
}
|
||||
// 如果是地址,初始化 addressFormat
|
||||
if (item.value === 'address') {
|
||||
newColumn.addressFormat = 'province/city/district/address' // 默认日期格式
|
||||
}
|
||||
|
||||
diyStore.editComponent.columnList.push(newColumn)
|
||||
}
|
||||
|
||||
const removeOption = (index: number) => {
|
||||
diyStore.editComponent.columnList.splice(index, 1)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// nextTick(() => {
|
||||
// if (diyStore.editComponent.columnList.length < 2) return;
|
||||
// const sortable = Sortable.create(imageBoxRef.value, {
|
||||
// group: 'item-wrap',
|
||||
// animation: 200,
|
||||
// onEnd: event => {
|
||||
// const temp = diyStore.editComponent.columnList[event.oldIndex!]
|
||||
// diyStore.editComponent.columnList.splice(event.oldIndex!, 1)
|
||||
// diyStore.editComponent.columnList.splice(event.newIndex!, 0, temp)
|
||||
// sortable.sort(
|
||||
// range(diyStore.editComponent.columnList.length).map(value => {
|
||||
// return value.toString()
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
console.log(diyStore.editComponent.columnList)
|
||||
})
|
||||
|
||||
const activeColumn = ref<any>({}) // 真正数据(原始数据,不动它)
|
||||
const activeColumnTemp = ref<any>({}) // 弹窗编辑临时副本
|
||||
const activeRadioIndex = ref(0) // 当前编辑列的下标
|
||||
|
||||
const radioDialogVisible = ref(false)
|
||||
const radioBoxRef = ref()
|
||||
|
||||
const optionsValue = ref('')
|
||||
const visible = ref(false)
|
||||
const dateFormat: any = reactive({
|
||||
format1: '',
|
||||
format2: '',
|
||||
format3: '',
|
||||
format4: ''
|
||||
})
|
||||
|
||||
const openRadioDialog = (item, index) => {
|
||||
activeRadioIndex.value = index // 记录当前列的下标,方便确定时更新
|
||||
activeColumn.value = item
|
||||
activeColumnTemp.value = JSON.parse(JSON.stringify(item)) // 深拷贝,避免联动
|
||||
if (item.type == 'radio') {
|
||||
if (!activeColumnTemp.value.options) activeColumnTemp.value.options = []
|
||||
radioDialogVisible.value = true
|
||||
// nextTick(() => initRadioSortable()) // 拖拽初始化
|
||||
} else if (item.type == 'date') {
|
||||
// 初始赋值当天日期
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(today.getDate()).padStart(2, '0')
|
||||
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
dateFormat.format1 = `${year}年${month}月${day}日`
|
||||
dateFormat.format2 = `${year}-${month}-${day}`
|
||||
dateFormat.format3 = `${year}/${month}/${day}`
|
||||
dateFormat.format4 = `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
radioDialogVisible.value = true
|
||||
} else if (item.type == 'address') {
|
||||
radioDialogVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化拖拽
|
||||
// const initRadioSortable = () => {
|
||||
// Sortable.create(radioBoxRef.value, {
|
||||
// group: 'radio-option-wrap',
|
||||
// animation: 200,
|
||||
// draggable: '.drag-radio-item',
|
||||
// onEnd: event => {
|
||||
// const options = activeColumnTemp.value.options // 注意!这里用 temp 的
|
||||
// const temp = options[event.oldIndex!]
|
||||
// options.splice(event.oldIndex!, 1)
|
||||
// options.splice(event.newIndex!, 0, temp)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
const handleDialogConfirm = () => {
|
||||
console.log(activeColumnTemp.value)
|
||||
|
||||
diyStore.editComponent.columnList[activeRadioIndex.value] = JSON.parse(JSON.stringify(activeColumnTemp.value)) // 同步副本到原数据
|
||||
radioDialogVisible.value = false // 关闭弹窗
|
||||
}
|
||||
|
||||
const addOptionItem = () => {
|
||||
const newOption = { id: generateId(), label: '选项' + (activeColumnTemp.value.options.length + 1) }
|
||||
activeColumnTemp.value.options.push(newOption)
|
||||
}
|
||||
|
||||
const addOtherOption = () => {
|
||||
const newOption = { id: generateId(), label: '其他' }
|
||||
activeColumnTemp.value.options.push(newOption)
|
||||
}
|
||||
|
||||
const removeOptionItem = (index: number) => {
|
||||
activeColumnTemp.value.options.splice(index, 1)
|
||||
}
|
||||
|
||||
// 数组去重
|
||||
const uniqueByKey = (arr: any, key: any) => {
|
||||
const seen = new Set()
|
||||
return arr.filter((item: any) => {
|
||||
const serializedKey = JSON.stringify(item[key])
|
||||
return seen.has(serializedKey) ? false : seen.add(serializedKey)
|
||||
})
|
||||
}
|
||||
// 批量添加
|
||||
const batchAddOptions = () => {
|
||||
if (optionsValue.value.trim()) {
|
||||
const newOptions = optionsValue.value.split(',').map((option: any) => {
|
||||
return {
|
||||
id: diyStore.generateRandom(),
|
||||
label: option.trim()
|
||||
}
|
||||
}).filter((option: any) => option.label !== '')
|
||||
|
||||
// 去除重复的选项
|
||||
const uniqueNewOptions = uniqueByKey(newOptions, 'label')
|
||||
|
||||
// 过滤掉已存在的选项
|
||||
const filteredNewOptions = uniqueNewOptions.filter(newOption =>
|
||||
!activeColumnTemp.value.options.some(existingOption => existingOption.label === newOption.label)
|
||||
)
|
||||
|
||||
// 如果有新的选项,添加到选项列表中
|
||||
if (filteredNewOptions.length > 0) {
|
||||
activeColumnTemp.value.options.push(...filteredNewOptions)
|
||||
} else {
|
||||
ElMessage({
|
||||
message: t('errorTipsTwo'),
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
|
||||
optionsValue.value = ''
|
||||
visible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.input-style .el-input__wrapper) {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.input-short{
|
||||
width: 80px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('defaultValue') }}</span>
|
||||
<el-tooltip effect="light" :content="t('defaultValueTips')" placement="top">
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model.trim="diyStore.editComponent.field.default"
|
||||
:placeholder="t('defaultValuePlaceholder')" clearable maxlength="18" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('rowCount')">
|
||||
<el-input v-model.trim="diyStore.editComponent.rowCount" :placeholder="t('rowCountPlaceholder')" clearable maxlength="2" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
<!--<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item class="display-block">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('隐私保护') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>会自动将提交的个人信息做加密展示。</p>
|
||||
<p>适用于公开展示收集的数据且不暴露用户隐私。</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-switch v-model="diyStore.editComponent.field.privacyProtection" />
|
||||
<div class="text-sm text-gray-400">{{ t('提交后自动隐藏文本,仅管理员可查看') }}</div>
|
||||
</el-form-item>
|
||||
</el-form>-->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
// todo 只需要考虑该组件自身的验证
|
||||
return res
|
||||
}
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('startTime') }}</h3>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.start.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('defaultValue')">
|
||||
<el-switch v-model="diyStore.editComponent.start.defaultControl" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="diyStore.editComponent.start.defaultControl">
|
||||
<el-radio-group v-model="diyStore.editComponent.start.timeWay">
|
||||
<el-radio label="current">{{ t('currentTime') }}</el-radio>
|
||||
<el-radio label="diy">{{ t('diyTime') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="diyStore.editComponent.start.defaultControl && diyStore.editComponent.start.timeWay == 'diy'">
|
||||
<el-time-picker v-model="diyStore.editComponent.field.default.start.date" :placeholder="t('startTimePlaceholder')" format="HH:mm" value-format="HH:mm" @change="startTimePickerChange" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('endTime') }}</h3>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.end.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('defaultValue')">
|
||||
<el-switch v-model="diyStore.editComponent.end.defaultControl" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="diyStore.editComponent.end.defaultControl">
|
||||
<el-radio-group v-model="diyStore.editComponent.end.timeWay">
|
||||
<el-radio label="current">{{ t('currentTime') }}</el-radio>
|
||||
<el-radio label="diy">{{ t('diyTime') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="diyStore.editComponent.end.defaultControl && diyStore.editComponent.end.timeWay == 'diy'">
|
||||
<el-time-picker :disabled-hours="disabledHours" :disabled-minutes="disabledMinutes" v-model="diyStore.editComponent.field.default.end.date" :placeholder="t('endTimePlaceholder')" format="HH:mm" value-format="HH:mm" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('textStyle') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('textFontSize')">
|
||||
<el-slider v-model="diyStore.editComponent.fontSize" show-input size="small" class="ml-[10px] diy-nav-slider" :min="12" :max="18" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('textFontWeight')">
|
||||
<el-radio-group v-model="diyStore.editComponent.fontWeight">
|
||||
<el-radio :label="'normal'">{{ t('fontWeightNormal') }}</el-radio>
|
||||
<el-radio :label="'bold'">{{ t('fontWeightBold') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('textColor')">
|
||||
<el-color-picker v-model="diyStore.editComponent.textColor" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
let starTime = diyStore.value[index].field.default.start.date
|
||||
let endTime = diyStore.value[index].field.default.end.date
|
||||
|
||||
const today = new Date()
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
|
||||
if (diyStore.editComponent.start.timeWay == 'current') {
|
||||
starTime = `${hours}:${minutes}`
|
||||
}
|
||||
if (diyStore.editComponent.end.timeWay == 'current') {
|
||||
endTime = `${hours}:${minutes}`
|
||||
}
|
||||
|
||||
if (diyStore.editComponent.start.defaultControl && starTime == '') {
|
||||
res.code = false
|
||||
res.message = t('startTimeTips')
|
||||
return res
|
||||
}
|
||||
if (diyStore.editComponent.end.defaultControl && endTime == '') {
|
||||
res.code = false
|
||||
res.message = t('endTimeTips')
|
||||
return res
|
||||
}
|
||||
|
||||
if (diyStore.editComponent.start.defaultControl && diyStore.editComponent.end.defaultControl && timeInvertSecond(starTime) > timeInvertSecond(endTime)) {
|
||||
res.code = false
|
||||
res.message = t('startEndTimeTips')
|
||||
return res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const today = new Date()
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
|
||||
if (!diyStore.editComponent.field.default.start.date) {
|
||||
diyStore.editComponent.field.default.start.date = `${hours}:${minutes}`
|
||||
diyStore.editComponent.field.default.start.timestamp = timeInvertSecond(`${hours}:${minutes}`)
|
||||
}
|
||||
if (!diyStore.editComponent.field.default.end.date) {
|
||||
const endDate = new Date()
|
||||
endDate.setHours(today.getHours(), today.getMinutes() + 10, 0, 0) // 在当前时间基础上加 10 分钟
|
||||
const endHours = String(endDate.getHours()).padStart(2, '0')
|
||||
const endMinutes = String(endDate.getMinutes()).padStart(2, '0')
|
||||
|
||||
diyStore.editComponent.field.default.end.date = `${endHours}:${endMinutes}`
|
||||
diyStore.editComponent.field.default.end.timestamp = timeInvertSecond(`${endHours}:${endMinutes}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 开始时间选择器
|
||||
const startTimePickerChange = (e) => {
|
||||
diyStore.editComponent.field.default.start.timestamp = timeInvertSecond(e)
|
||||
|
||||
const startTimeArr = e.split(':')
|
||||
const date = new Date()
|
||||
date.setHours(parseInt(startTimeArr[0]), parseInt(startTimeArr[1]), 0, 0)
|
||||
date.setMinutes(date.getMinutes() + 10)
|
||||
const updatedEndTime = `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
||||
diyStore.editComponent.field.default.end.date = updatedEndTime
|
||||
diyStore.editComponent.field.default.end.timestamp = timeInvertSecond(updatedEndTime)
|
||||
}
|
||||
|
||||
// 结束时间选择器
|
||||
const endTimePickerChange = (e) => {
|
||||
diyStore.editComponent.field.default.end.timestamp = timeInvertSecond(e)
|
||||
}
|
||||
|
||||
const disabledHours = () => {
|
||||
const timeArr = diyStore.editComponent.field.default.start.date.split(':')
|
||||
return makeRange(0, timeArr[0])
|
||||
}
|
||||
|
||||
const disabledMinutes = (hour: number) => {
|
||||
const timeArr = diyStore.editComponent.field.default.start.date.split(':')
|
||||
return makeRange(0, timeArr[1])
|
||||
}
|
||||
|
||||
const makeRange = (start: number, end: number) => {
|
||||
const result: number[] = []
|
||||
for (let i = start; i < end; i++) {
|
||||
result.push(i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const timeInvertSecond = (time: any) => {
|
||||
const arr = time.split(':')
|
||||
let num = 0
|
||||
if (arr[0]) {
|
||||
num += arr[0] * 60 * 60
|
||||
}
|
||||
if (arr[1]) {
|
||||
num += arr[1] * 60
|
||||
}
|
||||
if (arr[2]) {
|
||||
num += arr[2]
|
||||
}
|
||||
return num
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('defaultValue')">
|
||||
<el-switch v-model="diyStore.editComponent.defaultControl" @change="changeDateDefaultControl" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="diyStore.editComponent.defaultControl">
|
||||
<el-radio-group v-model="diyStore.editComponent.timeWay">
|
||||
<el-radio label="current">{{ t('currentTime') }}</el-radio>
|
||||
<el-radio label="diy">{{ t('diyTime') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="diyStore.editComponent.defaultControl && diyStore.editComponent.timeWay == 'diy'">
|
||||
<el-time-picker v-model="diyStore.editComponent.field.default" :placeholder="t('timePlaceholder')" format="HH:mm" value-format="HH:mm" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
return res
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始赋值当天时间
|
||||
if (!diyStore.editComponent.field.default) {
|
||||
const today = new Date()
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
diyStore.editComponent.field.default = `${hours}:${minutes}`
|
||||
}
|
||||
})
|
||||
|
||||
const changeDateDefaultControl = (val: any) => {
|
||||
if (val) {
|
||||
const today = new Date()
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
diyStore.editComponent.field.default = `${hours}:${minutes}`
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => diyStore.editComponent.timeWay,
|
||||
(newVal) => {
|
||||
const today = new Date()
|
||||
const hours = String(today.getHours()).padStart(2, '0')
|
||||
const minutes = String(today.getMinutes()).padStart(2, '0')
|
||||
diyStore.editComponent.field.default = `${hours}:${minutes}`
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-[3px]">{{ t('上传方式') }}</span>
|
||||
<el-tooltip effect="light" :content="t('拍摄时长限制1分钟,从相册上传不限制时长。')" placement="top">
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-radio-group v-model="diyStore.editComponent.uploadMode">
|
||||
<el-radio label="shoot_and_album">{{ t('拍摄和相册') }}</el-radio>
|
||||
<el-radio label="shoot_only">{{ t('只允许拍摄') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
|
||||
<!-- 表单组件 字段内容设置 -->
|
||||
<slot name="field"></slot>
|
||||
<el-form label-width="100px" class="px-[10px]" @submit.prevent>
|
||||
<el-form-item :label="t('formPlaceholder')">
|
||||
<el-input v-model.trim="diyStore.editComponent.placeholder" :placeholder="t('formPlaceholderTips')" clearable maxlength="15" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 表单组件 其他设置 -->
|
||||
<slot name="other"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
|
||||
<!-- 表单组件 字段样式 -->
|
||||
<slot name="style-field"></slot>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref } from 'vue'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = ['componentBgUrl'] // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
const res = { code: true, message: '' }
|
||||
// todo 只需要考虑该组件自身的验证
|
||||
return res
|
||||
}
|
||||
defineExpose({})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form :inline="true" :model="tableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('formSelectContentTitle')" prop="title" class="form-item-wrap">
|
||||
<el-input v-model.trim="tableData.searchParam.title" :placeholder="t('formSelectContentTitlePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('formSelectContentTypeName')" prop="type" class="form-item-wrap">
|
||||
<el-select v-model="tableData.searchParam.type" :placeholder="t('formSelectContentTypeNamePlaceholder')">
|
||||
<el-option :label="t('formSelectContentTypeAll')" value="" />
|
||||
<el-option v-for="(item, key) in formType" :label="item.title" :value="key" :key="key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="form-item-wrap">
|
||||
<el-button type="primary" @click="loadList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="tableData.data" size="large" ref="tableRef" v-loading="tableData.loading">
|
||||
<template #empty>
|
||||
<span>{{ !tableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column min-width="7%">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.checked" @change="handleCheckChange($event,row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="page_title" :label="t('formSelectContentTitle')" min-width="65%" />
|
||||
<el-table-column prop="type_name" :label="t('formSelectContentTypeName')" min-width="25%" />
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="tableData.total"
|
||||
@size-change="loadList()" @current-change="loadList" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, nextTick } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getFormType, getDiyFormSelectPageList } from '@/app/api/diy_form'
|
||||
import { FormInstance, ElMessage } from 'element-plus'
|
||||
|
||||
const prop = defineProps({
|
||||
formId: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const formType: any = reactive({}) // 表单类型
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
const tableRef = ref()
|
||||
|
||||
const tableData: any = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
title: '',
|
||||
type: '',
|
||||
verify_form_ids: []
|
||||
}
|
||||
})
|
||||
|
||||
// 已选万能表单
|
||||
const selectData: any = reactive({
|
||||
form_id: prop.formId
|
||||
})
|
||||
|
||||
// 获取自定义表单列表
|
||||
const loadList = (page: number = 1) => {
|
||||
tableData.loading = true
|
||||
tableData.page = page
|
||||
|
||||
if (selectData.form_id) {
|
||||
tableData.searchParam.verify_form_ids = [selectData.form_id]
|
||||
}
|
||||
|
||||
getDiyFormSelectPageList({
|
||||
page: tableData.page,
|
||||
limit: tableData.limit,
|
||||
...tableData.searchParam
|
||||
}).then(res => {
|
||||
tableData.loading = false
|
||||
tableData.data = res.data.data
|
||||
|
||||
tableData.data.forEach((item: any) => {
|
||||
item.checked = item.form_id == selectData.form_id
|
||||
})
|
||||
tableData.total = res.data.total
|
||||
setGoodsSelected()
|
||||
}).catch(() => {
|
||||
tableData.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
// 获取万能表单类型
|
||||
const loadFormType = (addon = '') => {
|
||||
getFormType({}).then(res => {
|
||||
for (const key in formType) {
|
||||
delete formType[key]
|
||||
}
|
||||
|
||||
for (const key in res.data) {
|
||||
formType[key] = res.data[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadFormType()
|
||||
loadList()
|
||||
|
||||
const handleCheckChange = (isSelect: any, row: any) => {
|
||||
if (isSelect) {
|
||||
selectData.form_id = row.form_id
|
||||
} else {
|
||||
selectData.form_id = 0 // 未选中,移除当前
|
||||
}
|
||||
setGoodsSelected()
|
||||
}
|
||||
|
||||
// 表格设置选中状态
|
||||
const setGoodsSelected = () => {
|
||||
nextTick(() => {
|
||||
for (let i = 0; i < tableData.data.length; i++) {
|
||||
tableData.data[i].checked = false
|
||||
if (selectData.form_id == tableData.data[i].form_id) {
|
||||
tableData.data[i].checked = true
|
||||
Object.assign(selectData, tableData.data[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
loadList()
|
||||
}
|
||||
|
||||
const getData = () => {
|
||||
if (selectData.form_id == 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: `${t('formSelectContentTips')}`
|
||||
})
|
||||
return
|
||||
}
|
||||
return {
|
||||
name: 'DIY_FORM',
|
||||
title: selectData.page_title,
|
||||
url: `/app/pages/index/diy_form?form_id=${selectData.form_id}`,
|
||||
action: '',
|
||||
formId: selectData.form_id
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-item-wrap {
|
||||
margin-right: 10px !important;
|
||||
margin-bottom: 10px !important;
|
||||
|
||||
&.last-child {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,435 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="showDialog" :title="t('submitSuccess')" width="850px" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
|
||||
<div class="flex flex-1 mt-[24px] mx-[24px] mb-0">
|
||||
<div class="preview-wrap">
|
||||
<div class="absolute z-1 left-0 top-0">
|
||||
<img src="@/app/assets/images/diy_form/mobile_tabbar.png" class="w-[324px]" />
|
||||
</div>
|
||||
<div class="absolute z-1 left-0 bottom-0">
|
||||
<img src="@/app/assets/images/diy_form/mobile_bottom.png" class="w-[324px]" />
|
||||
</div>
|
||||
|
||||
<div class="page-wrap">
|
||||
<div class="px-[13px] flex flex-col items-center flex-1">
|
||||
<div class="flex items-center justify-center w-[48px] h-[48px] text-[40px] p-[4px] mt-[32px] mb-[16px] mx-auto rounded-[50%]">
|
||||
<el-icon><SuccessFilled color="#20bf64" /></el-icon>
|
||||
</div>
|
||||
<div class="record-name">
|
||||
<span class="text-[#1E1E1E] font-bold text-[24px]" v-if="formData.tips_type == 'default'">{{ t('writeSuccess') }}</span>
|
||||
<span class="text-[#1E1E1E] font-bold text-[16px]" v-else-if="formData.tips_type == 'diy'">{{ formData.tips_text ? formData.tips_text : '填写成功' }}</span>
|
||||
</div>
|
||||
<div class="to-detail">
|
||||
<div class="text-[14px] mt-[16px] py-[4px] px[8px] text-[#576b95]">{{ t('viewFillingDetails') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative pt-[8px] pb-[48px] h-[112px]">
|
||||
<div v-if="formData.success_after_action.finish" class="!mt-[16px] rounded-[3px] mx-auto text-[15px] w-[100px] min-w-[160px] h-[32px] leading-[32px] text-center max-w-[274px] truncate bg-[#20bf64] text-[#ffffff]">
|
||||
<div class="text-[15px]">{{ t('finish') }}</div>
|
||||
</div>
|
||||
<div v-if="formData.success_after_action.goback" class="!mt-[16px] rounded-[3px] mx-auto text-[15px] w-[100px] min-w-[160px] h-[32px] leading-[32px] text-center max-w-[274px] truncate bg-[#f2f2f2] text-[#353535]">
|
||||
<div class="text-[14px]">{{ t('back') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 核销凭证 todo 后续完善 -->
|
||||
<!-- <div class="page-wrap verify-voucher-wrap" style="display:none;">-->
|
||||
|
||||
<!-- <div class="tips-wrap">感谢你的填写,以下是你的核销凭证</div>-->
|
||||
<!-- <div class="qrcode-wrap">-->
|
||||
<!-- <div class="text-[14px] text-[#333]">请妥善保存你的核销凭证</div>-->
|
||||
<!-- <div class="text-[20px] font-bold text-[#333] my-[10px]">现场出示凭证</div>-->
|
||||
<!-- <el-image class="w-[180px]" :src="wapImage" />-->
|
||||
<!-- <div class="text-primary mt-[10px]">保存凭证</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="relative pt-[8px] pb-[48px] h-[112px]">-->
|
||||
<!-- <div class="!mt-[16px] rounded-[3px] mx-auto text-[15px] w-[100px] min-w-[160px] h-[32px] leading-[32px] text-center max-w-[274px] truncate bg-[#20bf64] text-[#ffffff]">-->
|
||||
<!-- <div class="text-[15px]">返回二维码</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="!mt-[16px] rounded-[3px] mx-auto text-[15px] w-[100px] min-w-[160px] h-[32px] leading-[32px] text-center max-w-[274px] truncate bg-[#fff] text-[#353535]">-->
|
||||
<!-- <div class="text-[14px]">完成</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="text-[14px] mt-[16px] py-[4px] px[8px] text-[#576b95]">查看填写详情</div>-->
|
||||
|
||||
<!-- </div>-->
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="item-wrap">
|
||||
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('afterSubmission') }}</div>
|
||||
<el-radio-group v-model="formData.submit_after_action" class="!block">
|
||||
<el-radio label="text" class="!flex">
|
||||
<span class="mr-[3px]">{{ t('displayTextMessages') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>{{ t('displayTextMessagesTips') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-radio>
|
||||
<!-- todo 后续完善 -->
|
||||
<!-- <el-radio label="voucher" class="!flex">-->
|
||||
<!-- <span class="mr-[3px]">{{ t('获取核销凭证') }}</span>-->
|
||||
<!-- <el-tooltip effect="light" placement="top">-->
|
||||
<!-- <template #content>-->
|
||||
<!-- <p>{{ t('提交后页面会将提交的表单记录内容生成二维码并展示,可选择设置两种不同的二维码内容。适合核销、数据录入等场景。') }}</p>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-icon>-->
|
||||
<!-- <QuestionFilled color="#999999" />-->
|
||||
<!-- </el-icon>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </el-radio>-->
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="item-wrap" v-if="formData.submit_after_action == 'text'">
|
||||
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('promptText') }}</div>
|
||||
<div>
|
||||
<el-radio-group v-model="formData.tips_type" class="!block">
|
||||
<el-radio label="default" class="!block">
|
||||
<span class="mr-[3px]">{{ t('defaultPrompt') }}</span>
|
||||
<span class="!text-[#999] text-[12px] ml-[8px]">{{ t('defaultPromptTips') }}</span>
|
||||
</el-radio>
|
||||
<el-radio label="diy" class="!block">{{ t('diyPrompt') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<el-input v-if="formData.tips_type == 'diy'" v-model.trim="formData.tips_text" :placeholder="t('tipsTextPlaceholder')" class="w-[350px]" maxlength="30" clearable show-word-limit />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 核销凭证 todo 后续完善 -->
|
||||
<template v-else-if="formData.submit_after_action == 'voucher'">
|
||||
|
||||
<div class="item-wrap">
|
||||
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('validityPeriodOfVoucher') }}</div>
|
||||
<div>
|
||||
<el-radio-group v-model="formData.time_limit_type" class="!block">
|
||||
<el-radio label="no_limit" class="!block">{{ t('noLimit') }}</el-radio>
|
||||
<el-radio label="specify_time" class="!block">
|
||||
<span class="mr-[3px]">{{ t('specifyTime') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p>{{ t('specifyTimeTips') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-radio>
|
||||
<el-radio label="submission_time" class="!block">
|
||||
<span class="mr-[3px]">{{ t('submissionTime') }}</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p class="w-[250px]">{{ t('submissionTimeTips') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<el-date-picker v-if="formData.time_limit_type == 'specify_time'" v-model="formData.validity_time" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
|
||||
<div class="flex items-center mt-[5px]" v-if="formData.time_limit_type == 'submission_time'">
|
||||
<span>{{ t('afterSubmissionRecords') }}</span>
|
||||
<!-- <div class="flex items-center px-[5px]">-->
|
||||
<!-- v-model.trim="formData.length"-->
|
||||
<el-input v-model.trim="formData.submission_time_value" @keyup="filterNumber($event)" size="small" clearable class="!w-[100px] px-[5px]" maxlength="3" />
|
||||
<el-select v-model="formData.timeUnit" clearable class="!w-[100px] pr-[5px]" size="small">
|
||||
<el-option v-for="(item, index) in validityOptions" :key="item.value" :label="item.text" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
<span>{{ t('effective') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-wrap">
|
||||
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">{{ t('voucherStyle') }}</div>
|
||||
<div>
|
||||
<el-form-item :label="t('titleAboveTheCode')">
|
||||
<!-- v-model.trim="formData.active_name"-->
|
||||
<el-input clearable :placeholder="t('titleAboveTheCodePlaceholder')" class="input-width" :maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('contentAboveTheCode')">
|
||||
<el-input clearable :placeholder="t('contentAboveTheCodePlaceholder')" class="input-width" :maxlength="20" />
|
||||
<div>
|
||||
<span class="text-primary cursor-pointer mr-[10px]">{{ t('addLinefeeds') }}</span>
|
||||
<span class="text-primary cursor-pointer">{{ t('addFields') }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('contentBelowTheCode')">
|
||||
<div class="block">
|
||||
<el-checkbox class="!block" :label="t('submissionRecordTime')" value="" />
|
||||
|
||||
<el-checkbox class="!block !h-[20px]" :label="t('currentTime')" value="" />
|
||||
<div class="text-[#999] ml-[22px]">{{ t('currentTimeTips') }}</div>
|
||||
|
||||
<el-checkbox class="!block" :label="t('dispalyPromptText')" value="" />
|
||||
<el-input class="ml-[22px]" :rows="4" type="textarea" :placeholder="t('tipsTextPlaceholder')" maxlength="100" />
|
||||
|
||||
<el-checkbox class="!block" :label="t('voucherDeadline')" value="" />
|
||||
<el-checkbox class="!block" :label="t('saveVoucher')" value="" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- todo 后续完善 -->
|
||||
<div class="item-wrap">
|
||||
<div class="text-[16px] h-[24px] font-bold text-[#262626] mb-[16px] w-[140px] mr-[32px] flex-shrink-0">
|
||||
<span>{{ t('subsequentPperationButtons') }}</span>
|
||||
<!-- <p class="text-[12px] text-[#999] mt-[4px] font-normal">最多选择2个</p>-->
|
||||
</div>
|
||||
<div class="content-list-wrap">
|
||||
<!-- <el-checkbox-group :min="1" :max="2">-->
|
||||
<!-- <el-checkbox v-model="formData.success_after_action.share" label="转发填写内容" value="share">-->
|
||||
<!-- <div class="text-[#333]">转发填写内容</div>-->
|
||||
<!-- </el-checkbox>-->
|
||||
<!-- <p class="text-[#999] text-[12px] pl-[24px] mt-[4px]">提交表单后,可转发给微信好友查看。支持按钮文案自定义,提醒填表人转发给特定人员查看</p>-->
|
||||
|
||||
<el-checkbox v-model="formData.success_after_action.finish" :label="t('finish')" value="finish">
|
||||
<div class="text-[#333]">{{ t('finish') }}</div>
|
||||
</el-checkbox>
|
||||
<p class="text-[#999] text-[12px] pl-[24px] mt-[4px]">{{ t('finishTips') }}</p>
|
||||
|
||||
<el-checkbox v-model="formData.success_after_action.goback" :label="t('back')" value="goback">
|
||||
<div class="text-[#333]">{{ t('back') }}</div>
|
||||
</el-checkbox>
|
||||
<p class="text-[#999] text-[12px] pl-[24px] mt-[4px]">{{ t('backTips') }}</p>
|
||||
<!-- </el-checkbox-group>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" @click="confirm">{{ t('save') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/lang'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import QRCode from 'qrcode'
|
||||
import storage from '@/utils/storage'
|
||||
import { filterNumber } from '@/utils/common'
|
||||
import { getUrl } from '@/app/api/sys'
|
||||
import { getFormSubmitConfig,editDiyFormSubmitConfig } from '@/app/api/diy_form'
|
||||
|
||||
const showDialog = ref(false)
|
||||
const repeat = ref(false)
|
||||
|
||||
/**
|
||||
* 表单数据
|
||||
*/
|
||||
const initialFormData = {
|
||||
id: 0,
|
||||
form_id: 0,
|
||||
submit_after_action: 'text', // 填表人提交后操作,text:文字信息,voucher:核销凭证
|
||||
tips_type: 'default', // 提示内容类型,default:默认提示,diy:自定义提示
|
||||
tips_text: '', // 自定义提示内容
|
||||
time_limit_type: 'no_limit', // 核销凭证有效期限制类型,no_limit:不限制,specify_time:指定固定开始结束时间,submission_time:按提交时间设置有效期
|
||||
// 核销凭证时间限制规则,json格式 todo 结构待定,后续完善
|
||||
time_limit_rule: {
|
||||
validity_time: [], // 指定固定开始结束时间
|
||||
submission_time_value: '', // 按提交时间设置有效期
|
||||
timeUnit: 'day', // 提交时间单位
|
||||
},
|
||||
// 核销凭证内容,json格式 todo 结构待定,后续完善
|
||||
voucher_content_rule: {},
|
||||
// 填写成功后续操作
|
||||
success_after_action: {
|
||||
share: false, // 转发填写内容
|
||||
finish: true, // 完成
|
||||
goback: true, // 返回
|
||||
}
|
||||
}
|
||||
|
||||
const formData: Record<string, any> = reactive({ ...initialFormData })
|
||||
|
||||
const wapUrl = ref('')
|
||||
const wapDomain = ref('')
|
||||
const wapImage = ref('')
|
||||
const wapPreview = ref('')
|
||||
const page = ref('')
|
||||
|
||||
// 核销凭证有效期
|
||||
const validityOptions = reactive([
|
||||
{
|
||||
text:'天',
|
||||
value:'day'
|
||||
},
|
||||
{
|
||||
text:'周',
|
||||
value:'week'
|
||||
},
|
||||
{
|
||||
text:'月',
|
||||
value:'month'
|
||||
},
|
||||
{
|
||||
text:'年',
|
||||
value:'year'
|
||||
},
|
||||
{
|
||||
text:'分钟',
|
||||
value:'minutes'
|
||||
}
|
||||
])
|
||||
|
||||
// getUrl().then((res: any) => {
|
||||
// wapUrl.value = res.data.wap_url
|
||||
//
|
||||
// // 生产模式禁止
|
||||
// if (import.meta.env.MODE == 'production') return
|
||||
//
|
||||
// wapDomain.value = res.data.wap_domain
|
||||
//
|
||||
// // env文件配置过wap域名
|
||||
// if (wapDomain.value) {
|
||||
// wapUrl.value = wapDomain.value + '/wap'
|
||||
// }
|
||||
//
|
||||
// const wapDomainStorage = storage.get('wap_domain')
|
||||
// if (wapDomainStorage) {
|
||||
// wapUrl.value = wapDomainStorage
|
||||
// }
|
||||
// })
|
||||
|
||||
const loadQrcode = () => {
|
||||
wapPreview.value = `${wapUrl.value}${page.value}`
|
||||
// errorCorrectionLevel:密度容错率L(低)H(高)
|
||||
QRCode.toDataURL(wapPreview.value, { errorCorrectionLevel: 'L', margin: 0, width: 120 }).then(url => {
|
||||
wapImage.value = url
|
||||
})
|
||||
}
|
||||
|
||||
const emit = defineEmits(['complete'])
|
||||
|
||||
const setFormData = async (row: any = null) => {
|
||||
Object.assign(formData, initialFormData)
|
||||
if (row) {
|
||||
const data = await (await getFormSubmitConfig(row.form_id)).data
|
||||
if (data) {
|
||||
Object.keys(formData).forEach((key: string) => {
|
||||
if (data[key] != undefined) formData[key] = data[key]
|
||||
})
|
||||
}
|
||||
// todo 靠后完善
|
||||
// page.value = `/app/pages/index/diy_form?form_id=${formData.form_id}`
|
||||
// loadQrcode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认
|
||||
*/
|
||||
const confirm = () => {
|
||||
if(formData.tips_type == 'diy' && !formData.tips_text){
|
||||
ElMessage.error('提示不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (repeat.value) return;
|
||||
repeat.value = true
|
||||
|
||||
const data = formData
|
||||
|
||||
editDiyFormSubmitConfig(data).then(res => {
|
||||
repeat.value = false
|
||||
showDialog.value = false
|
||||
emit('complete')
|
||||
}).catch(err => {
|
||||
repeat.value = false
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showDialog,
|
||||
setFormData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.preview-wrap {
|
||||
position: relative;
|
||||
width: 324px;
|
||||
min-height: 555px;
|
||||
padding: 82px 12px 20px;
|
||||
background-size: 100%;
|
||||
background-repeat: repeat-y;
|
||||
background-image: url(../../../../app/assets/images/diy_form/mobile_line.png);
|
||||
border-radius: 38px;
|
||||
overflow: hidden;
|
||||
box-shadow: none;
|
||||
background-color: #fff !important;
|
||||
margin-right: 24px;
|
||||
overflow-y: auto;
|
||||
|
||||
.page-wrap {
|
||||
position: relative;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
text-align: center;
|
||||
min-height: 548px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.verify-voucher-wrap {
|
||||
background-color: #f4f4f4;
|
||||
.tips-wrap{
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
color: rgba(0,0,0,.65);
|
||||
margin: 20px 10px;
|
||||
}
|
||||
.qrcode-wrap{
|
||||
border-radius: 12px;
|
||||
margin: 0 20px;
|
||||
background: #fff;
|
||||
padding: 20px 10px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-wrap {
|
||||
padding: 20px 24px 24px;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 1px;
|
||||
width: calc(100% - 48px);
|
||||
background-color: hsla(210, 8%, 51%, .13);
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&:last-child:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<el-dialog v-model="showDialog" :title="t('writeSet')" width="600px" class="diy-dialog-wrap" :close-on-press-escape="true" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
|
||||
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
|
||||
|
||||
<!-- <el-form-item :label="t('填写方式')">-->
|
||||
<!-- <el-radio-group v-model="formData.write_way">-->
|
||||
<!-- <el-radio label="no_limit">{{t('不限制')}}</el-radio>-->
|
||||
<!-- <el-radio label="scan">{{t('仅限扫一扫')}}</el-radio>-->
|
||||
<!-- <el-radio label="url">{{t('仅限链接进入')}}</el-radio>-->
|
||||
<!-- </el-radio-group>-->
|
||||
<!-- </el-form-item>-->
|
||||
|
||||
<el-form-item :label="t('joinMemberType')">
|
||||
<el-radio-group v-model="formData.join_member_type">
|
||||
<el-radio label="all_member">{{t('allMember')}}</el-radio>
|
||||
<el-radio label="selected_member_level">{{t('selectedMemberLevel')}}</el-radio>
|
||||
<el-radio label="selected_member_label">{{t('selectedMemberLabel')}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 会员标签 -->
|
||||
<el-form-item :label="t('memberLabel')" prop="label_ids" v-if="formData.join_member_type=='selected_member_label'">
|
||||
<el-select v-model="formData.label_ids" clearable multiple :placeholder="t('memberLabelPlaceholder')" class="input-width">
|
||||
<el-option :label="item['label_name']" :value="item['label_id']" v-for="(item, index) in labelSelectData" :key="index" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 会员等级 -->
|
||||
<el-form-item :label="t('memberLevel')" prop="level_ids" v-if="formData.join_member_type=='selected_member_level'">
|
||||
<el-select v-model="formData.level_ids" clearable multiple :placeholder="t('memberLevelPlaceholder')" class="input-width">
|
||||
<el-option :label="item['level_name']" :value="item['level_id']" v-for="(item, index) in levelSelectData" :key="index" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('apieceFillQuantity')" :class="{ '!mb-[5px]' : formData.member_write_type == 'diy' }">
|
||||
<el-radio-group v-model="formData.member_write_type">
|
||||
<el-radio label="no_limit">{{t('noLimit')}}</el-radio>
|
||||
<el-radio label="diy">{{t('diy')}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label=" " v-if="formData.member_write_type == 'diy'" prop="member_write_rule">
|
||||
<div class="flex items-center">
|
||||
<span>每</span>
|
||||
<el-input v-model.trim="formData.member_write_rule.time_value" @keyup="filterNumber($event)" size="small" class="!w-[50px] px-[5px]" maxlength="3" />
|
||||
<el-select v-model="formData.member_write_rule.time_unit" class="!w-[60px] pr-[5px]" size="small">
|
||||
<el-option v-for="(item, index) in validityOptions" :key="item.value" :label="item.text" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
<span>可填写</span>
|
||||
<el-input v-model.trim="formData.member_write_rule.num" @keyup="filterNumber($event)" size="small" class="!w-[50px] px-[5px]" maxlength="3" />
|
||||
<span>次</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('fillQuantityTotal')" :class="{ '!mb-[5px]' : formData.form_write_type == 'diy' }">
|
||||
<el-radio-group v-model="formData.form_write_type">
|
||||
<el-radio label="no_limit">{{t('noLimit')}}</el-radio>
|
||||
<el-radio label="diy">{{t('diy')}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label=" " v-if="formData.form_write_type == 'diy'" prop="form_write_rule">
|
||||
<div class="flex items-center">
|
||||
<span>每</span>
|
||||
<el-input v-model.trim="formData.form_write_rule.time_value" @keyup="filterNumber($event)" size="small" class="!w-[50px] px-[5px]" maxlength="3" />
|
||||
<el-select v-model="formData.form_write_rule.time_unit" class="!w-[60px] pr-[5px]" size="small">
|
||||
<el-option v-for="(item, index) in validityOptions" :key="item.value" :label="item.text" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
<span>可填写</span>
|
||||
<el-input v-model.trim="formData.form_write_rule.num" @keyup="filterNumber($event)" size="small" class="!w-[50px] px-[5px]" maxlength="3" />
|
||||
<span class="mr-[5px]">次</span>
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<p class="w-[250px]">{{ t('writeTips') }}</p>
|
||||
</template>
|
||||
<el-icon>
|
||||
<QuestionFilled color="#999999" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('fillInTheTimePeriod')" prop="time_limit_rule">
|
||||
<el-radio-group v-model="formData.time_limit_type">
|
||||
<el-radio label="no_limit">{{t('noLimit')}}</el-radio>
|
||||
<el-radio label="specify_time">{{t('setSpecifyTime')}}</el-radio>
|
||||
<el-radio label="open_day_time">{{t('openDayTime')}}</el-radio>
|
||||
</el-radio-group>
|
||||
<el-date-picker v-if="formData.time_limit_type == 'specify_time'" v-model="formData.time_limit_rule.specify_time" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
|
||||
<div class="flex items-center mt-[5px]" v-if="formData.time_limit_type == 'open_day_time'">
|
||||
<span class="mr-[5px]">每天</span>
|
||||
<el-time-picker class="!w-[180px]" v-model="formData.time_limit_rule.open_day_time" format="HH:mm" value-format="HH:mm" is-range range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间" />
|
||||
<span class="ml-[5px]">可填写</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item :label="t('允许修改内容')" class="display-block">-->
|
||||
<!-- <el-switch v-model="formData.is_allow_update_content" :active-value="1" :inactive-value="0" />-->
|
||||
<!-- <div class="text-sm text-gray-400">{{ t('开启后,填表人可以修改自己填写的内容。') }}</div>-->
|
||||
<!-- </el-form-item>-->
|
||||
|
||||
<!-- <el-form-item :label="t('填写须知')">-->
|
||||
<!-- <el-input v-model.trim="formData.write_instruction" :placeholder="t('请输入填写须知')" type="textarea" maxlength="500" show-word-limit rows="5" class="w-[400px]" clearable />-->
|
||||
<!-- </el-form-item>-->
|
||||
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { filterNumber } from '@/utils/common'
|
||||
import {getMemberLabelAll,getMemberLevelAll } from '@/app/api/member'
|
||||
import { getFormWriteConfig,editDiyFormWriteConfig } from '@/app/api/diy_form'
|
||||
|
||||
const showDialog = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
/**
|
||||
* 表单数据
|
||||
*/
|
||||
const initialFormData = {
|
||||
id: 0,
|
||||
form_id: 0, // 万能表单id
|
||||
write_way: 'no_limit', // 填写方式,no_limit:不限制,scan:仅限微信扫一扫,url:仅限链接进入
|
||||
join_member_type: 'all_member', // 参与会员,all_member:所有会员参与,selected_member_level:指定会员等级,selected_member_label:指定会员标签
|
||||
level_ids: [], // 会员等级id集合
|
||||
label_ids: [], // 会员标签id集合
|
||||
member_write_type: 'no_limit', // 每人可填写次数,no_limit:不限制,diy:自定义
|
||||
// 每人可填写次数自定义规则
|
||||
member_write_rule: {
|
||||
time_value: 1, // 时间
|
||||
time_unit: 'day', // 时间单位
|
||||
num: 1 // 可填写次数
|
||||
},
|
||||
form_write_type: 'no_limit', // 表单可填写数量,no_limit:不限制,diy:自定义
|
||||
// 表单可填写总数自定义规则
|
||||
form_write_rule: {
|
||||
time_value: 1, // 时间
|
||||
time_unit: 'day', // 时间单位
|
||||
num: 1 // 可填写次数
|
||||
},
|
||||
time_limit_type: 'no_limit', // 填写时间限制类型,no_limit:不限制,specify_time:指定开始结束时间,open_day_time:设置每日开启时间
|
||||
// 填写时间限制规则
|
||||
time_limit_rule: {
|
||||
specify_time: [], // 指定开始结束时间
|
||||
open_day_time: [], // 设置每日开启时间
|
||||
},
|
||||
is_allow_update_content: 0, // 是否允许修改自己填写的内容,0:否,1:是
|
||||
write_instruction: '', // 表单填写须知
|
||||
}
|
||||
|
||||
const formData: Record<string, any> = reactive({ ...initialFormData })
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
label_ids: [
|
||||
{ required: true, message: t('labelTips'), trigger: 'blur' }
|
||||
],
|
||||
level_ids: [
|
||||
{ required: true, message: t('levelTips'), trigger: 'blur' }
|
||||
],
|
||||
member_write_rule: [
|
||||
{
|
||||
validator: (rule: any, value: string, callback: any) => {
|
||||
let unit = ''
|
||||
validityOptions.forEach((item,index)=>{
|
||||
if(item.value == value.time_unit){
|
||||
unit = item.text;
|
||||
}
|
||||
})
|
||||
if(formData.member_write_type == 'diy'){
|
||||
if(!value.time_value){
|
||||
callback(new Error(`${unit}数不能为空`))
|
||||
}else if(!value.num){
|
||||
callback(new Error(t('numCannotNull')))
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
form_write_rule: [
|
||||
{
|
||||
validator: (rule: any, value: string, callback: any) => {
|
||||
let unit = ''
|
||||
validityOptions.forEach((item,index)=>{
|
||||
if(item.value == value.time_unit){
|
||||
unit = item.text;
|
||||
}
|
||||
})
|
||||
if(formData.member_write_type == 'diy'){
|
||||
if(!value.time_value){
|
||||
callback(new Error(`${unit}数不能为空`))
|
||||
}else if(!value.num){
|
||||
callback(new Error(t('numCannotNull')))
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
}else{
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
time_limit_rule: [
|
||||
{
|
||||
validator: (rule: any, value: string, callback: any) => {
|
||||
if (formData.time_limit_type == 'specify_time' && (!value.specify_time || !value.specify_time.length)) {
|
||||
callback(new Error(t('timeLimitRuleOne')))
|
||||
} else if (formData.time_limit_type == 'open_day_time' && (!value.open_day_time || !value.open_day_time.length)) {
|
||||
callback(new Error(t('timeLimitRuleTwo')))
|
||||
} else if (formData.time_limit_type == 'open_day_time' && value.open_day_time && value.open_day_time.length) {
|
||||
if (value.open_day_time[0] == value.open_day_time[1]) {
|
||||
callback(new Error(t('timeLimitRuleThree')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const levelSelectData = ref([])
|
||||
const labelSelectData = ref([])
|
||||
|
||||
// 获取全部标签
|
||||
getMemberLabelAll().then(({ data }) => {
|
||||
labelSelectData.value = data
|
||||
})
|
||||
|
||||
getMemberLevelAll().then(({ data }) => {
|
||||
levelSelectData.value = data
|
||||
})
|
||||
|
||||
const validityOptions = reactive([
|
||||
{
|
||||
text:'天',
|
||||
value:'day'
|
||||
},
|
||||
{
|
||||
text:'周',
|
||||
value:'week'
|
||||
},
|
||||
{
|
||||
text:'月',
|
||||
value:'month'
|
||||
},
|
||||
{
|
||||
text:'年',
|
||||
value:'year'
|
||||
}
|
||||
])
|
||||
|
||||
const emit = defineEmits(['complete'])
|
||||
|
||||
const setFormData = async (row: any = null) => {
|
||||
Object.assign(formData, initialFormData)
|
||||
loading.value = true
|
||||
if (row) {
|
||||
const data = await (await getFormWriteConfig(row.form_id)).data
|
||||
if (data && Object.keys(data).length) {
|
||||
Object.keys(formData).forEach((key: string) => {
|
||||
if (data[key] != undefined) formData[key] = data[key]
|
||||
})
|
||||
}else{
|
||||
formData.form_id = row.form_id;
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认
|
||||
* @param formEl
|
||||
*/
|
||||
const confirm = async (formEl: FormInstance | undefined) => {
|
||||
if (loading.value || !formEl) return
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
|
||||
const data = formData
|
||||
|
||||
editDiyFormWriteConfig(data).then(res => {
|
||||
loading.value = false
|
||||
showDialog.value = false
|
||||
emit('complete')
|
||||
}).catch(err => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const filterSpecial = (event:any) => {
|
||||
event.target.value = event.target.value.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '')
|
||||
event.target.value = event.target.value.replace(/[`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]/g, '')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showDialog,
|
||||
setFormData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss">
|
||||
.diy-dialog-wrap .el-form-item__label{
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.display-block {
|
||||
.el-form-item__content {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1022
admin-vben/src/app/views/diy_form/edit.vue
Normal file
1022
admin-vben/src/app/views/diy_form/edit.vue
Normal file
File diff suppressed because it is too large
Load Diff
590
admin-vben/src/app/views/diy_form/list.vue
Normal file
590
admin-vben/src/app/views/diy_form/list.vue
Normal file
@@ -0,0 +1,590 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-page-title">{{ pageName }}</span>
|
||||
<el-button type="primary" class="w-[100px]" @click="dialogVisible = true">{{ t('addDiyForm') }}</el-button>
|
||||
</div>
|
||||
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="diyFormTableData.searchParam" ref="searchFormDiyFormRef">
|
||||
<el-form-item :label="t('title')" prop="title">
|
||||
<el-input v-model.trim="diyFormTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item :label="t('forAddon')" prop="addon_name">-->
|
||||
<!-- <el-select v-model="diyFormTableData.searchParam.addon_name" :placeholder="t('forAddonPlaceholder')" @change="handleSelectAddonChange">-->
|
||||
<!-- <el-option :label="t('all')" value="" />-->
|
||||
<!-- <el-option v-for="(item, key) in apps" :label="item.title" :value="key" :key="key"/>-->
|
||||
<!-- </el-select>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item :label="t('typeName')" prop="type">
|
||||
<el-select v-model="diyFormTableData.searchParam.type" :placeholder="t('formTypePlaceholder')">
|
||||
<el-option :label="t('all')" value="" />
|
||||
<el-option v-for="(item, key) in formType" :label="item.title" :value="key" :key="key"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadDiyFormList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormDiyFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<div class="mb-[10px] flex items-center">
|
||||
<el-checkbox v-model="toggleCheckbox" size="large" class="px-[14px]" @change="toggleChange" :indeterminate="isIndeterminate" />
|
||||
<el-button @click="batchDeleteForms" size="small">{{t("batchDeletion")}}</el-button>
|
||||
</div>
|
||||
<el-table :data="diyFormTableData.data" size="large" ref="diyFormListTableRef" v-loading="diyFormTableData.loading" @selection-change="handleSelectionChange">
|
||||
<template #empty>
|
||||
<span>{{ !diyFormTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="page_title" :label="t('title')" min-width="120" />
|
||||
<!-- <el-table-column prop="addon_name" :label="t('forAddon')" min-width="80" />-->
|
||||
<el-table-column prop="type_name" :label="t('typeName')" min-width="80" />
|
||||
<el-table-column :label="t('status')" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="success" v-if="row.status == 1" class="cursor-pointer" @click="showClick(row)">{{ t('statusOn') }}</el-tag>
|
||||
<el-tag type="info" v-else class="cursor-pointer" @click="showClick(row)">{{ t('statusOff') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="update_time" :label="t('updateTime')" min-width="120" />
|
||||
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center justify-end">
|
||||
<el-button type="primary" v-if="row.status == 1 && row.type=='DIY_FORM'" link @click="spreadEvent(row)">{{ t('promotion') }}</el-button>
|
||||
<!-- <el-button type="primary" link @click="toPreview(row)">{{ t('preview') }}</el-button>-->
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<el-button v-if="row.status == 0" type="primary" link @click="deleteEvent(row.form_id)">{{ t('delete') }}</el-button>
|
||||
<el-button type="primary" link @click="detailEvent(row)">{{ t('detail') }}</el-button>
|
||||
<el-dropdown placement="bottom" trigger="click" class="ml-[12px]">
|
||||
<el-button type="primary" link>{{ t('more') }}</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="row.type=='DIY_FORM'">
|
||||
<el-button type="primary" class="w-full" link @click="submitConfigEvent(row)">{{ t('submitSuccess') }}</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button type="primary" class="w-full" link @click="writeConfigEvent(row)">{{ t('writeSet') }}</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="row.type=='DIY_FORM'">
|
||||
<el-button type="primary" class="w-full" link @click="openShare(row)">{{ t('shareSet') }}</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button type="primary" class="w-full" link @click="exportEvent(row)">{{ t('export') }}</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button type="primary" class="w-full" link @click="copyEvent(row.form_id)">{{ t('copy') }}</el-button>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="diyFormTableData.page" v-model:page-size="diyFormTableData.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="diyFormTableData.total"
|
||||
@size-change="loadDiyFormList()" @current-change="loadDiyFormList" />
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
|
||||
<!--添加表单-->
|
||||
<el-dialog v-model="dialogVisible" :title="t('addFormTips')" width="980px">
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules">
|
||||
<!-- <el-form-item :label="t('title')" prop="title">-->
|
||||
<!-- <el-input v-model.trim="formData.title" :placeholder="t('titlePlaceholder')" clearable maxlength="12" show-word-limit class="w-full" />-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item prop="type">
|
||||
<div class="image-selection-container">
|
||||
<div
|
||||
v-for="(item, key) in formType"
|
||||
:key="key"
|
||||
class="image-option"
|
||||
:class="{ selected: formData.type === key }"
|
||||
@click="selectType(key)"
|
||||
>
|
||||
<img :src="img(item.preview)" class="option-image" />
|
||||
<div class="option-title">{{ item.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <el-select v-model="formData.type" :placeholder="t('formTypePlaceholder')" class="!w-full">
|
||||
<el-option v-for="(item, key) in formType" :label="item.title" :value="key" :key="key"/>
|
||||
</el-select> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" @click="addEvent(formRef)">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 分享设置-->
|
||||
<el-dialog v-model="shareDialogVisible" :title="t('shareSet')" width="30%">
|
||||
<el-tabs v-model="tabShareType">
|
||||
<el-tab-pane :label="t('wechat')" name="wechat"></el-tab-pane>
|
||||
<el-tab-pane :label="t('weapp')" name="weapp"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form :model="shareFormData[tabShareType]" label-width="90px" ref="shareFormRef" :rules="shareFormRules">
|
||||
<el-form-item :label="t('sharePage')">
|
||||
<span>{{ sharePage }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareTitle')" prop="title">
|
||||
<el-input v-model.trim="shareFormData[tabShareType].title" :placeholder="t('shareTitlePlaceholder')" clearable maxlength="30" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareDesc')" prop="desc" v-if="tabShareType == 'wechat'">
|
||||
<el-input v-model.trim="shareFormData[tabShareType].desc" :placeholder="t('shareDescPlaceholder')" type="textarea" rows="4" clearable maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('shareImageUrl')" prop="url">
|
||||
<upload-image v-model="shareFormData[tabShareType].url" :limit="1" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="shareDialogVisible = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" @click="shareEvent(shareFormRef)">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 推广弹出框 -->
|
||||
<spread-popup ref="spreadPopupRef" />
|
||||
|
||||
<!-- 表单提交成功页弹出框 -->
|
||||
<form-submit-popup ref="formSubmitPopupRef" @complete="loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)" />
|
||||
|
||||
<!-- 表单填写设置弹出框 -->
|
||||
<form-write-popup ref="formWritePopupRef" @complete="loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)" />
|
||||
|
||||
<records-detail ref="recordsDetailDialog"/>
|
||||
|
||||
<!-- 表单明细导出弹出框 -->
|
||||
<export-sure ref="exportSureDialog" :show="flag" type="diy_form_records" :searchParam="diyFormDetailData" @close="handleExportClose" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getFormType, getApps, getDiyFormPageList, deleteDiyForm, editDiyFormShare, editFormStatus, copyForm } from '@/app/api/diy_form'
|
||||
import { FormInstance, ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { setTablePageStorage, getTablePageStorage, img } from '@/utils/common'
|
||||
|
||||
import recordsDetail from '@/app/views/diy_form/records.vue'
|
||||
import formSubmitPopup from '@/app/views/diy_form/components/form-submit-popup.vue'
|
||||
import formWritePopup from '@/app/views/diy_form/components/form-write-popup.vue'
|
||||
import spreadPopup from '@/components/spread-popup/index.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const pageName = route.meta.title
|
||||
const repeat = ref(false)
|
||||
|
||||
const formType: any = reactive({}) // 表单类型
|
||||
|
||||
// 添加自定义表单
|
||||
const formData = reactive({
|
||||
title: '',
|
||||
type: ''
|
||||
})
|
||||
|
||||
// 详情
|
||||
const recordsDetailDialog: Record<string, any> | null = ref(null)
|
||||
const detailEvent = (row: any) => {
|
||||
const data = { form_id: row.form_id }
|
||||
recordsDetailDialog.value.setFormData(data)
|
||||
recordsDetailDialog.value.showDialog = true
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
title: [
|
||||
{ required: true, message: t('titlePlaceholder'), trigger: 'blur' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: t('formTypePlaceholder'), trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const dialogVisible = ref(false)
|
||||
const addEvent = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const query = { type: formData.type } // , title: formData.title
|
||||
const url = router.resolve({
|
||||
path: '/decorate/form/edit',
|
||||
query
|
||||
})
|
||||
window.open(url.href)
|
||||
dialogVisible.value = false
|
||||
formData.title = ''
|
||||
formData.type = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const showClick = (row: any) => {
|
||||
const data = row.status === 1 ? 0 : 1
|
||||
const obj = {
|
||||
form_id: row.form_id,
|
||||
status: data
|
||||
}
|
||||
editFormStatus(obj).then((res) => {
|
||||
row.status = data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取万能表单类型
|
||||
const loadFormType = (addon = '') => {
|
||||
getFormType({}).then(res => {
|
||||
for (const key in formType) {
|
||||
delete formType[key]
|
||||
}
|
||||
|
||||
for (const key in res.data) {
|
||||
formType[key] = res.data[key]
|
||||
}
|
||||
formData.type = Object.keys(formType)[0]
|
||||
})
|
||||
}
|
||||
|
||||
loadFormType()
|
||||
|
||||
const apps: any = reactive({}) // 应用插件列表
|
||||
|
||||
// todo 靠后完善
|
||||
// getApps({}).then(res=>{
|
||||
// if(res.data){
|
||||
// for (const key in res.data) {
|
||||
// apps[key] = res.data[key];
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// 根据所属插件,查询表单类型
|
||||
const handleSelectAddonChange = (value: any) => {
|
||||
diyFormTableData.searchParam.type = ''
|
||||
loadFormType(value)
|
||||
}
|
||||
|
||||
const diyFormTableData: any = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
title: '',
|
||||
type: '',
|
||||
mode: '',
|
||||
addon_name: ''
|
||||
}
|
||||
})
|
||||
|
||||
const searchFormDiyFormRef = ref<FormInstance>()
|
||||
|
||||
// 获取自定义表单列表
|
||||
const loadDiyFormList = (page: number = 1) => {
|
||||
diyFormTableData.loading = true
|
||||
diyFormTableData.page = page
|
||||
|
||||
getDiyFormPageList({
|
||||
page: diyFormTableData.page,
|
||||
limit: diyFormTableData.limit,
|
||||
...diyFormTableData.searchParam
|
||||
}).then(res => {
|
||||
diyFormTableData.loading = false
|
||||
diyFormTableData.data = res.data.data
|
||||
diyFormTableData.total = res.data.total
|
||||
setTablePageStorage(diyFormTableData.page, diyFormTableData.limit, diyFormTableData.searchParam)
|
||||
}).catch(() => {
|
||||
diyFormTableData.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
|
||||
const selectType = (index: number) => {
|
||||
formData.type = index.toString()
|
||||
}
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
loadDiyFormList()
|
||||
}
|
||||
|
||||
// 编辑自定义表单
|
||||
const editEvent = (data: any) => {
|
||||
const url = router.resolve({
|
||||
path: '/decorate/form/edit',
|
||||
query: { form_id: data.form_id }
|
||||
})
|
||||
window.open(url.href)
|
||||
}
|
||||
|
||||
// 复制页面
|
||||
const copyEvent = (id: any) => {
|
||||
ElMessageBox.confirm(t('diyFormCopyTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
if (repeat.value) return
|
||||
repeat.value = true
|
||||
|
||||
copyForm({ form_id: id }).then((res: any) => {
|
||||
if (res.code == 1) {
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
}
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 删除自定义表单
|
||||
const deleteEvent = (form_id: number) => {
|
||||
ElMessageBox.confirm(t('diyFormDeleteTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
deleteDiyForm({ form_ids: [form_id] }).then(() => {
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
}
|
||||
// 批量复选框
|
||||
const toggleCheckbox = ref()
|
||||
|
||||
// 复选框中间状态
|
||||
const isIndeterminate = ref(false)
|
||||
|
||||
// 监听批量复选框事件
|
||||
const toggleChange = (value: any) => {
|
||||
isIndeterminate.value = false
|
||||
diyFormListTableRef.value.toggleAllSelection()
|
||||
}
|
||||
|
||||
const diyFormListTableRef = ref()
|
||||
|
||||
// 选中数据
|
||||
const multipleSelection: any = ref([])
|
||||
|
||||
// 监听表格单行选中
|
||||
const handleSelectionChange = (val: []) => {
|
||||
multipleSelection.value = val
|
||||
|
||||
toggleCheckbox.value = false
|
||||
if (
|
||||
multipleSelection.value.length > 0 &&
|
||||
multipleSelection.value.length < diyFormTableData.data.length
|
||||
) {
|
||||
isIndeterminate.value = true
|
||||
} else {
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
|
||||
if (multipleSelection.value.length == diyFormTableData.data.length) {
|
||||
toggleCheckbox.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
const batchDeleteForms = () => {
|
||||
if (multipleSelection.value.length == 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: `${t('batchEmptySelectedFormsTips')}`
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(t('batchFormsDeleteTips'), t('warning'), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
if (repeat.value) return
|
||||
repeat.value = true
|
||||
|
||||
const form_ids: any = []
|
||||
multipleSelection.value.forEach((item: any) => {
|
||||
form_ids.push(item.form_id)
|
||||
})
|
||||
|
||||
deleteDiyForm({
|
||||
form_ids
|
||||
}).then(() => {
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
repeat.value = false
|
||||
}).catch(() => {
|
||||
repeat.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转去预览
|
||||
const toPreview = (data: any) => {
|
||||
const url = router.resolve({
|
||||
path: '/preview/wap',
|
||||
query: {
|
||||
page: '/app/pages/index/diy_form?form_id=' + data.form_id
|
||||
}
|
||||
})
|
||||
window.open(url.href)
|
||||
}
|
||||
|
||||
const tabShareType = ref('wechat')
|
||||
const sharePage = ref('')
|
||||
const shareFormId = ref(0)
|
||||
const shareFormData = reactive({
|
||||
wechat: {
|
||||
title: '',
|
||||
desc: '',
|
||||
url: ''
|
||||
},
|
||||
weapp: {
|
||||
title: '',
|
||||
url: ''
|
||||
}
|
||||
})
|
||||
|
||||
const shareDialogVisible = ref(false)
|
||||
const shareFormRules = computed(() => {
|
||||
return {}
|
||||
})
|
||||
|
||||
const shareFormRef = ref<FormInstance>()
|
||||
const openShare = async (row: any) => {
|
||||
shareFormId.value = row.form_id
|
||||
sharePage.value = row.title
|
||||
const share = row.share
|
||||
shareFormData.wechat = share.wechat
|
||||
shareFormData.weapp = share.weapp
|
||||
|
||||
shareDialogVisible.value = true
|
||||
}
|
||||
|
||||
const shareEvent = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
editDiyFormShare({
|
||||
form_id: shareFormId.value,
|
||||
share: JSON.stringify(shareFormData)
|
||||
}).then(() => {
|
||||
loadDiyFormList(getTablePageStorage(diyFormTableData.searchParam).page)
|
||||
shareDialogVisible.value = false
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 表单推广
|
||||
const spreadPopupRef = ref(null)
|
||||
|
||||
const spreadEvent = (data: any) => {
|
||||
const pagePath = '/app/pages/index/diy_form'
|
||||
const paramsArr = [
|
||||
{ name: 'form_id', value: data.form_id },
|
||||
];
|
||||
const title = '表单推广'
|
||||
const folder = 'diy_form'
|
||||
spreadPopupRef.value?.show(pagePath, paramsArr, title, folder);
|
||||
}
|
||||
|
||||
// 表单提交成功页弹出框
|
||||
const formSubmitPopupRef: any = ref(null)
|
||||
|
||||
const submitConfigEvent = (data: any) => {
|
||||
formSubmitPopupRef.value.setFormData(data)
|
||||
formSubmitPopupRef.value.showDialog = true
|
||||
}
|
||||
|
||||
// 表单填写设置弹出框
|
||||
const formWritePopupRef: any = ref(null)
|
||||
|
||||
const writeConfigEvent = (data: any) => {
|
||||
formWritePopupRef.value.setFormData(data)
|
||||
formWritePopupRef.value.showDialog = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单填写记录明细导出
|
||||
*/
|
||||
const exportSureDialog = ref(null)
|
||||
const flag = ref(false)
|
||||
const handleExportClose = (val) => {
|
||||
flag.value = val
|
||||
}
|
||||
|
||||
const diyFormDetailData: any = reactive({
|
||||
form_id: 0
|
||||
})
|
||||
const exportEvent = (data: any) => {
|
||||
diyFormDetailData.form_id = data.form_id
|
||||
flag.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-selection-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
.image-option {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 300px;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 10px;
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.image-option.selected {
|
||||
border-color: var(--el-color-primary);
|
||||
box-shadow: 0 0 10px var(--el-color-primary-light-9);
|
||||
}
|
||||
.option-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
}
|
||||
.option-title {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
390
admin-vben/src/app/views/diy_form/records.vue
Normal file
390
admin-vben/src/app/views/diy_form/records.vue
Normal file
@@ -0,0 +1,390 @@
|
||||
<template>
|
||||
<el-drawer v-model="showDialog" :title="t('dataAndStatistics')" direction="rtl" size="70%" :before-close="handleClose" class="member-detail-drawer">
|
||||
<el-tabs v-model="activeName" class="demo-tabs">
|
||||
<el-tab-pane :label="t('detailData')" name="detail_data">
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="formData.searchParam" ref="searchFormDiyFormRef">
|
||||
<el-form-item :label="t('fillInFormPerson')" prop="keyword">
|
||||
<el-input v-model.trim="formData.searchParam.keyword" :placeholder="t('fillInFormPersonplaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('fillInFormDate')" prop="create_time">
|
||||
<el-date-picker v-model="formData.searchParam.create_time" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadFormRecordsListFn()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormDiyFormRef)">{{ t('reset') }}</el-button>
|
||||
<el-button type="primary" @click="exportEvent">{{ t('export') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-table :data="formData.data" size="large" v-loading="formData.loading">
|
||||
<template #empty>
|
||||
<span>{{ !formData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column fixed :label="t('fillInFormPersonInfo')" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center cursor-pointer" @click="detailEvent(row.member.member_id)">
|
||||
<div class="min-w-[50px] h-[50px] flex items-center justify-center">
|
||||
<el-image v-if="row.member.headimg" class="w-[50px] h-[50px]" :src="img(row.member.headimg)" fit="contain">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<img class="w-[50px] h-[50px] rounded-full" src="@/app/assets/images/member_head.png" alt="">
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<img class="w-[50px] h-[50px] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<span :title="(row.member.nickname || row.member.username)" class="multi-hidden">{{row.member.nickname || row.member.username}}</span>
|
||||
<span class="text-primary text-[12px]">{{row.member.mobile || ''}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed prop="create_time" :label="t('fillInFormDate')" min-width="120" />
|
||||
|
||||
<el-table-column v-for="item in formFieldsList" :key="item.field_key" :label="item.field_name" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<!-- 动态渲染表单组件内容 -->
|
||||
<template v-if="row.recordsFieldList[item.field_key]">
|
||||
<component :is="row.recordsFieldList[item.field_key].detailComponent" :data="row.recordsFieldList[item.field_key]"/>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" min-width="70">
|
||||
<template #default="{ row }">
|
||||
<!-- <el-button type="primary" link @click="formDetailEvent(row)">{{ t('详情') }}</el-button> -->
|
||||
<el-button type="primary" link @click="deleteEvent(row)">{{ t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="formData.page" v-model:page-size="formData.limit" :page-sizes="[6,10,20,30,50,100]"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="formData.total"
|
||||
@size-change="loadFormRecordsListFn()" @current-change="loadFormRecordsListFn" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('fillInFormPersonStatics')" name="member_stat">
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="formMemberList.searchParam" ref="searchFormDiyMemberRef">
|
||||
<el-form-item :label="t('fillInFormPerson')" prop="keyword">
|
||||
<el-input v-model.trim="formMemberList.searchParam.keyword" :placeholder="t('fillInFormPersonplaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="getFormRecordsMemberFn()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetFormMember(searchFormDiyMemberRef)">{{ t('reset') }}</el-button>
|
||||
<el-button type="primary" @click="exportMemberEvent">{{ t('export') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-table :data="formMemberList.data" size="large" v-loading="formMemberList.loading">
|
||||
<template #empty>
|
||||
<span>{{ !formMemberList.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column fixed :label="t('fillInFormPersonInfo')" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center cursor-pointer" @click="detailEvent(row.member.member_id)">
|
||||
<div class="min-w-[50px] h-[50px] flex items-center justify-center">
|
||||
<el-image v-if="row.member.headimg" class="w-[50px] h-[50px]" :src="img(row.member.headimg)" fit="contain">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<img class="w-[50px] h-[50px] rounded-full" src="@/app/assets/images/member_head.png" alt="">
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<img class="w-[50px] h-[50px] rounded-full" v-else src="@/app/assets/images/member_head.png" alt="">
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<span :title="(row.member.nickname || row.member.username)" class="multi-hidden">{{row.member.nickname || row.member.username}}</span>
|
||||
<span class="text-primary text-[12px]">{{row.member.mobile || ''}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column fixed prop="create_time" :label="t('填表时间')" min-width="120" /> -->
|
||||
<el-table-column fixed prop="create_time" :label="t('fillInFormTotal')" min-width="500">
|
||||
<template #default="{ row }">
|
||||
{{ row.write_count }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="formMemberList.page" v-model:page-size="formMemberList.limit"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="formMemberList.total"
|
||||
@size-change="getFormRecordsMemberFn()" @current-change="getFormRecordsMemberFn()" />
|
||||
</div>
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('fieldStatistics')" name="field_stat">
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="formData.searchParam" ref="searchFormDiyFieldsRef">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="exportFieldsEvent">{{ t('export') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-collapse v-model="activeNames" class="diy-collapse mt-[15px]">
|
||||
<el-collapse-item :title="item.field_name" :name="item.field_id" v-for="(item, index) in formFieldsStat" :key="index">
|
||||
<template #title>
|
||||
<div class="text-[16px] font-bold">{{item.field_name}}</div>
|
||||
</template>
|
||||
<el-table :data="item.value_list" border>
|
||||
<el-table-column :label="item.field_name" prop="render_value">
|
||||
<template #default="{ row }">
|
||||
{{row.render_value ? row.render_value : '未填写'}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="小计" prop="write_count"></el-table-column>
|
||||
<el-table-column label="比例">
|
||||
<template #default="{ row }">
|
||||
<el-progress :percentage="row.write_percent"></el-progress>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="t('viewInformation')" width="400px">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex mb-[10px]" v-for="(item, index) in formDetail" :key="index">
|
||||
<div class="flex justify-end w-[100px]">{{ item.label }}:</div>
|
||||
<div class="flex ml-[20px]">
|
||||
<template v-if="Array.isArray(item.text)">
|
||||
<div class="mr-[10px]" v-for="(textItem, i) in item.text" :key="i">
|
||||
{{ textItem }}
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>{{ item.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="dialogVisible = false">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</el-drawer>
|
||||
<export-sure ref="exportSureDialog" :show="flag" type="diy_form_records" :searchParam="formData.searchParam" @close="handleExportClose" />
|
||||
<export-sure ref="exportSureDialog" :show="flagMember" type="diy_form_records_member" :searchParam="formMemberList.searchParam" @close="handleMemberExportClose" />
|
||||
<export-sure ref="exportSureDialog" :show="flagFields" type="diy_form_records_fields" :searchParam="formData.searchParam" @close="handleFieldsExportClose" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, defineAsyncComponent } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getDiyFormFieldsList, getDiyFormFieldStat, getFormRecords, getFormRecordsInfo, deleteFormRecords, getFormRecordsMember } from '@/app/api/diy_form'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { img } from '@/utils/common'
|
||||
import { ElMessageBox, FormInstance } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const showDialog = ref(false)
|
||||
const activeName = ref('detail_data')
|
||||
const formId = ref(0)
|
||||
const dialogVisible = ref(false)
|
||||
const searchFormDiyFormRef = ref<FormInstance>()
|
||||
const searchFormDiyMemberRef = ref<FormInstance>()
|
||||
const searchFormDiyFieldsRef = ref<FormInstance>()
|
||||
const handleClose = (done: () => void) => {
|
||||
showDialog.value = false
|
||||
}
|
||||
|
||||
const formData = reactive({
|
||||
page: 1,
|
||||
limit: 6,
|
||||
total: 0,
|
||||
loading: false,
|
||||
data: [],
|
||||
searchParam: {
|
||||
form_id: 0,
|
||||
keyword: '',
|
||||
create_time: ''
|
||||
}
|
||||
})
|
||||
|
||||
const formFieldsList = ref([])
|
||||
|
||||
// 获取万能表单字段列表
|
||||
const getDiyFormFieldsListFn = (form_id: any) => {
|
||||
getDiyFormFieldsList({
|
||||
form_id,
|
||||
order: 'field_id',
|
||||
sort: 'asc'
|
||||
}).then((res: any) => {
|
||||
formFieldsList.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取字段统计列表
|
||||
const formFieldsStat = ref([])
|
||||
const getDiyFormFieldStatFn = (form_id: any) => {
|
||||
getDiyFormFieldStat({
|
||||
form_id
|
||||
}).then((res: any) => {
|
||||
formFieldsStat.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const modules: any = import.meta.glob('@/**/*.vue')
|
||||
const formDetail = ref([])
|
||||
|
||||
const formDetailEvent = (row: any) => {
|
||||
getFormRecordsInfo(row.record_id).then((res:any) => {
|
||||
formDetail.value = res.data.value
|
||||
dialogVisible.value = true
|
||||
})
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteEvent = (row: any) => {
|
||||
ElMessageBox.confirm(t('deleteTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
deleteFormRecords({
|
||||
record_id: row.record_id,
|
||||
form_id: row.form_id
|
||||
}).then(() => {
|
||||
initData()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
loadFormRecordsListFn()
|
||||
}
|
||||
const resetFormMember = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
getFormRecordsMemberFn()
|
||||
}
|
||||
|
||||
const loadFormRecordsListFn = (page: number = 1) => {
|
||||
formData.loading = true
|
||||
formData.page = page
|
||||
getFormRecords({
|
||||
page: formData.page,
|
||||
limit: formData.limit,
|
||||
...formData.searchParam
|
||||
}).then((res: any) => {
|
||||
formData.loading = false
|
||||
formData.data = res.data.data
|
||||
formData.data.forEach((item: any) => {
|
||||
for (const key: any in item.recordsFieldList) {
|
||||
if (modules[item.recordsFieldList[key].detailComponent]) {
|
||||
item.recordsFieldList[key].detailComponent && (item.recordsFieldList[key].detailComponent = defineAsyncComponent(modules[item.recordsFieldList[key].detailComponent]))
|
||||
}
|
||||
}
|
||||
})
|
||||
formData.total = res.data.total
|
||||
}).catch(() => {
|
||||
formData.loading = false
|
||||
})
|
||||
}
|
||||
const formMemberList = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
data: [],
|
||||
searchParam: {
|
||||
keyword: '',
|
||||
form_id: 0
|
||||
}
|
||||
})
|
||||
|
||||
const getFormRecordsMemberFn = (page: number = 1) => {
|
||||
formMemberList.loading = true
|
||||
formMemberList.page = page
|
||||
getFormRecordsMember({
|
||||
page: formMemberList.page,
|
||||
limit: formMemberList.limit,
|
||||
...formMemberList.searchParam
|
||||
}).then((res: any) => {
|
||||
formMemberList.data = res.data.data
|
||||
formMemberList.total = res.total
|
||||
formMemberList.loading = false
|
||||
}).catch((error) => {
|
||||
formMemberList.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
// 查看会员详情
|
||||
const detailEvent = (member_id:number) => {
|
||||
const routeData = router.resolve(`/member/detail?id=${member_id}`)
|
||||
window.open(routeData.href, ' blank')
|
||||
}
|
||||
|
||||
const setFormData = async (row: any = null) => {
|
||||
formId.value = row.form_id
|
||||
formData.searchParam.form_id = row.form_id
|
||||
formMemberList.searchParam.form_id = row.form_id
|
||||
|
||||
getDiyFormFieldsListFn(row.form_id)
|
||||
initData()
|
||||
}
|
||||
|
||||
const initData = () => {
|
||||
getFormRecordsMemberFn()
|
||||
getDiyFormFieldStatFn(formId.value)
|
||||
loadFormRecordsListFn()
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单填写记录明细导出
|
||||
*/
|
||||
const exportSureDialog = ref(null)
|
||||
const flag = ref(false)
|
||||
const handleExportClose = (val) => {
|
||||
flag.value = val
|
||||
}
|
||||
const exportEvent = () => {
|
||||
flag.value = true
|
||||
}
|
||||
|
||||
const flagMember = ref(false)
|
||||
const handleMemberExportClose = (val) => {
|
||||
flagMember.value = val
|
||||
}
|
||||
const exportMemberEvent = () => {
|
||||
flagMember.value = true
|
||||
}
|
||||
|
||||
const flagFields = ref(false)
|
||||
const handleFieldsExportClose = (val) => {
|
||||
flagFields.value = val
|
||||
}
|
||||
const exportFieldsEvent = () => {
|
||||
flagFields.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showDialog,
|
||||
setFormData
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.diy-collapse .el-collapse-item__header{
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
.el-icon.el-collapse-item__arrow{
|
||||
margin-left: inherit;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user