🧹 清理重复配置文件

- 删除根目录中重复的 NestJS 配置文件
- 删除 tsconfig.json, tsconfig.build.json, eslint.config.mjs, .prettierrc
- 保留 wwjcloud-nest/ 目录中的完整配置
- 避免配置冲突,确保项目结构清晰
This commit is contained in:
wanwu
2025-10-14 23:56:20 +08:00
parent 7a160dd04b
commit e7a1d6b4d6
3263 changed files with 356 additions and 112679 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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密度容错率LH(高)
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>

View File

@@ -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, // 是否允许修改自己填写的内容01
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>

File diff suppressed because it is too large Load Diff

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

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