hookehuyr

✨ feat(多选框控件): 样式和组件逻辑修改

......@@ -27,6 +27,8 @@ declare module '@vue/runtime-core' {
NoteField: typeof import('./src/components/NoteField/index.vue')['default']
NumberField: typeof import('./src/components/NumberField/index.vue')['default']
NutButton: typeof import('@nutui/nutui-taro')['Button']
NutCheckbox: typeof import('@nutui/nutui-taro')['Checkbox']
NutCheckboxGroup: typeof import('@nutui/nutui-taro')['CheckboxGroup']
NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider']
NutDialog: typeof import('@nutui/nutui-taro')['Dialog']
NutField: typeof import('@nutui/nutui-taro')['Field']
......
<!--
* @Date: 2022-08-30 11:34:19
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-02-10 10:06:43
* @FilePath: /data-table/src/components/CheckboxField/index.vue
* @LastEditTime: 2023-04-04 11:03:21
* @FilePath: /custom_form/src/components/CheckboxField/index.vue
* @Description: 多项选择控件
-->
<template>
<div v-if="HideShow" class="checkbox-field-page">
<div class="label">
<span v-if="item.component_props.required" style="color: red">&nbsp;*</span>
<text v-if="item.component_props.required" style="color: red">&nbsp;*</text>
{{ item.component_props.label }}
<span v-if="item.component_props.max" style="color: gray">
<text v-if="item.component_props.max" style="color: gray">
(最多可选数:&nbsp;{{ item.component_props.max }})
</span>
</text>
</div>
<div v-if="item.component_props.note" class="note" v-html="item.component_props.note" />
<van-field :rules="item.rules" :border="false">
<template #input>
<van-checkbox-group v-model="checkbox_value" :direction="item.component_props.direction"
<nut-checkbox-group v-model="checkbox_value" :direction="item.component_props.direction"
:max="item.component_props.max" style="width: 100%">
<div v-for="x in item.component_props.options" :key="x.title" class="checkbox-wrapper">
<van-checkbox @click="onClick(x)" :name="x.title" icon-size="1rem" shape="square"
:checked-color="themeVars.radioColor" style="margin-bottom: 0.25rem">{{ x.title }}</van-checkbox>
<van-field v-if="checkbox_value.includes(x.value) && x.is_input" @blur="onBlur(x)" v-model="x.affix"
label=" " label-width="5px" :placeholder="x.input_placeholder" :rules="x.input_required ? rules : ''"
<nut-checkbox @click="onClick(x)" :label="x.title" icon-size="16" style="margin-bottom: 0.25rem">{{ x.title }}</nut-checkbox>
<nut-input
v-if="checkbox_value.includes(x.value) && x.is_input"
@blur="onBlur(x)"
v-model="x.affix"
label=" " label-width="5px" :placeholder="x.input_placeholder" :border="false"
:required="x.input_required" class="affix-input" />
<div
v-if="x.show_error"
style="padding: 5px 20px; color: red; font-size: 12px;"
>
{{ x.error_msg }}
</div>
</div>
</nut-checkbox-group>
<div
v-if="show_error"
style="padding: 5px 20px; color: red; font-size: 12px;"
>
{{ error_msg }}
</div>
</van-checkbox-group>
</template>
</van-field>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted, reactive } from "vue";
import { styleColor } from "@/constant.js";
const props = defineProps({
......@@ -51,30 +62,29 @@ const HideShow = computed(() => {
return !props.item.component_props.disabled
})
// TODO: 等待数据结构更新,看看怎么判断必填
// 校验函数返回 true 表示校验通过,false 表示不通过
const validator = (val) => {
if (!val) {
return false;
} else {
return true;
}
};
// 错误提示文案
const validatorMessage = (val, rule) => {
if (!val) {
return "补充信息不能为空";
}
};
const rules = [{ validator, message: validatorMessage }];
const emit = defineEmits(["active"]);
const checkbox_value = ref(props.item.component_props.default);
const affix_value = ref({});
const options = props.item.component_props.options;
const clearAffix = () => {
// 为选中项目的补充清空
options.forEach(element => {
if (!checkbox_value.value.includes(element.value)) {
element.affix = ''
}
element.show_error = false;
element.error_msg = '';
});
}
const onClick = (item) => {
item.checked = !item.checked;
handleEmit(item)
handleEmit(item);
// 清空错误提示
clearAffix();
// 校验输入项数据
validCheckbox();
}
const onBlur = (item) => {
handleEmit(item)
......@@ -91,34 +101,71 @@ const handleEmit = (item) => {
props.item.value = { key: props.item.key, value: checkbox_value.value, affix: affix_value.value, type: "checkbox" };
emit("active", props.item.value);
}
onMounted(() => {
// 新增错误提示标识
options.forEach(element => {
element.show_error = false;
element.error_msg = '';
});
// 发送自定义数据结构
props.item.value = { key: props.item.key, value: checkbox_value.value, affix: affix_value.value, type: "checkbox" };
emit("active", props.item.value);
})
});
const show_error = ref(false);
const error_msg = ref('');
// 校验模块
const validCheckbox = () => {
// 必填项
props.item.component_props.options.some(item => {
// 必选项勾选校验
if (!checkbox_value.value.length && item.input_required) {
show_error.value = true;
error_msg.value = '必填项不能为空';
return true;
}
// 必选项勾选,校验补充信息填写情况校验
else if (checkbox_value.value.includes(item.value) && item.is_input && item.input_required && !item.affix) {
show_error.value = true;
error_msg.value = '';
item.show_error = true;
item.error_msg = '补充信息不能为空';
return true;
} else {
show_error.value = false;
error_msg.value = '';
item.show_error = false;
item.error_msg = '';
}
});
return !show_error.value;
};
defineExpose({ validCheckbox });
</script>
<style lang="less" scoped>
<style lang="less">
.checkbox-field-page {
.label {
padding: 1rem 1rem 0 1rem;
font-size: 0.9rem;
padding: 30px 30px 0 30px;
font-size: 26px;
font-weight: bold;
}
.note {
font-size: 0.9rem;
font-size: 30px;
margin-left: 1rem;
color: gray;
padding-bottom: 0.5rem;
padding-bottom: 0;
white-space: pre-wrap;
}
.checkbox-wrapper {
border: 1px solid #eaeaea;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
margin-bottom: 0.25rem;
padding: 25px;
margin: 25px;
}
.affix-input {
border: 1px solid #eaeaea;
......
......@@ -2,7 +2,7 @@ import _ from '@/utils/lodash'
import TextField from '@/components/TextField/index.vue'
import TextareaField from '@/components/TextareaField/index.vue'
import RadioField from '@/components/RadioField/index.vue'
// import CheckboxField from '@/components/CheckboxField/index.vue'
import CheckboxField from '@/components/CheckboxField/index.vue'
// import PickerField from '@/components/PickerField/index.vue'
// import AreaPickerField from '@/components/AreaPickerField/index.vue'
// import DatePickerField from '@/components/DatePickerField/index.vue'
......@@ -87,9 +87,9 @@ export function createComponentType(data) {
if (item.component_props.tag === 'radio') {
item.component = RadioField
}
// if (item.component_props.tag === 'checkbox') {
// item.component = CheckboxField
// }
if (item.component_props.tag === 'checkbox') {
item.component = CheckboxField
}
// if (item.component_props.tag === 'select') {
// item.component = PickerField
// }
......
<!--
* @Date: 2023-03-24 09:19:27
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-04-03 16:52:30
* @LastEditTime: 2023-04-04 10:21:43
* @FilePath: /custom_form/src/pages/table/index.vue
* @Description: 文件描述
-->
......@@ -63,7 +63,7 @@ import { storeToRefs } from 'pinia'
import { mainStore } from '@/stores'
import { queryFormAPI, postVerifyPasswordAPI } from "@/api/form.js";
import { addFormDataAPI } from "@/api/data.js";
import { wxInfo, getUrlParams } from "@/utils/tools";
import { wxInfo, getUrlParams, deepClone } from "@/utils/tools";
import { styleColor } from "@/constant.js";
import { sharePage } from '@/composables/useShare.js'
// 初始化WX环境
......@@ -129,6 +129,7 @@ const formatData = (data) => {
const input = ref([]);
const textarea = ref([]);
const radio = ref([]);
const checkbox = ref([]);
const area_picker = ref([]);
const image_uploader = ref([]);
const file_uploader = ref([]);
......@@ -146,6 +147,9 @@ const setRefMap = (el, item) => {
if (item.component_props.tag === "radio") {
radio.value.push(el);
}
if (item.component_props.tag === "checkbox") {
checkbox.value.push(el);
}
if (item.component_props.tag === "area_picker") {
area_picker.value.push(el);
}
......@@ -378,6 +382,17 @@ const onActive = (item) => {
if (item.type === "radio") { // 单选控件
postData.value = Object.assign(postData.value, { [item.key]: item.affix ? item.affix : item.value });
}
if (item.type === "checkbox") { // 多选控件
const checkbox_value = deepClone(item.value)
checkbox_value.forEach((element, index) => {
for (const key in item.affix) {
if (item.affix[key] && element === key) {
checkbox_value[index] = item.affix[key]
}
}
});
postData.value = Object.assign(postData.value, { [item.key]: checkbox_value });
}
if (item.key === "area_picker") {
postData.value[item.filed_name] = item.value;
}
......@@ -396,17 +411,6 @@ const onActive = (item) => {
if (item.type === "picker") { // 下拉框控件
postData.value = _.assign(postData.value, { [item.key]: item.value });
}
if (item.type === "checkbox") { // 多选控件
const checkbox_value = _.cloneDeep(item.value)
checkbox_value.forEach((element, index) => {
for (const key in item.affix) {
if (item.affix[key] && element === key) {
checkbox_value[index] = item.affix[key]
}
}
});
postData.value = _.assign(postData.value, { [item.key]: checkbox_value });
}
// 检查规则,会影响字段显示
checkRules();
};
......@@ -453,6 +457,18 @@ const validOther = () => {
}
});
}
if (checkbox.value) {
// 单选框
checkbox.value.forEach((item, index) => {
if (!checkbox.value[index].validCheckbox()) {
valid = {
status: checkbox.value[index].validCheckbox(),
key: "checkbox",
};
return false;
}
});
}
if (area_picker.value) {
// 省市区地址
area_picker.value.forEach((item, index) => {
......
/*
* @Date: 2022-04-18 15:59:42
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-02-24 16:13:06
* @FilePath: /data-table/src/utils/tools.js
* @LastEditTime: 2023-04-04 10:21:01
* @FilePath: /custom_form/src/utils/tools.js
* @Description: 文件描述
*/
import dayjs from 'dayjs';
import dayjs from 'dayjs'
// 格式化时间
const formatDate = (date) => {
return dayjs(date).format('YYYY-MM-DD HH:mm');
};
return dayjs(date).format('YYYY-MM-DD HH:mm')
}
/**
* @description 判断浏览器属于平台
* @returns
*/
const wxInfo = () => {
let u = navigator.userAgent;
let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //android终端或者uc浏览器
let isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
let isMobile = u.indexOf('Android') > -1 || u.indexOf('iPhone') > -1 || u.indexOf('iPad') > -1; // 移动端平台
let isIpad = u.indexOf('iPad') > -1; // iPad平台
let uAgent = navigator.userAgent.toLowerCase();
let isWeiXin = (uAgent.match(/MicroMessenger/i) == 'micromessenger') ? true : false;
let isPC = (uAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone|micromessenger)/i)) ? false : true;
let u = navigator.userAgent
let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1 //android终端或者uc浏览器
let isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) //ios终端
let isMobile =
u.indexOf('Android') > -1 ||
u.indexOf('iPhone') > -1 ||
u.indexOf('iPad') > -1 // 移动端平台
let isIpad = u.indexOf('iPad') > -1 // iPad平台
let uAgent = navigator.userAgent.toLowerCase()
let isWeiXin =
uAgent.match(/MicroMessenger/i) == 'micromessenger' ? true : false
let isPC = uAgent.match(
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone|micromessenger)/i,
)
? false
: true
return {
isAndroid,
isiOS,
isWeiXin,
isMobile,
isIpad,
isPC
};
};
isPC,
}
}
/**
* @description 判断多行省略文本
......@@ -41,8 +49,8 @@ const wxInfo = () => {
* @returns
*/
const hasEllipsis = (id) => {
let oDiv = document.getElementById(id);
let flag = false;
let oDiv = document.getElementById(id)
let flag = false
if (oDiv.scrollHeight > oDiv.clientHeight) {
flag = true
}
......@@ -54,14 +62,15 @@ const hasEllipsis = (id) => {
* @param {*} url
* @returns
*/
const parseQueryString = url => {
var json = {};
var arr = url.indexOf('?') >= 0 ? url.substr(url.indexOf('?') + 1).split('&') : [];
arr.forEach(item => {
var tmp = item.split('=');
json[tmp[0]] = decodeURIComponent(tmp[1]);
});
return json;
const parseQueryString = (url) => {
var json = {}
var arr =
url.indexOf('?') >= 0 ? url.substr(url.indexOf('?') + 1).split('&') : []
arr.forEach((item) => {
var tmp = item.split('=')
json[tmp[0]] = decodeURIComponent(tmp[1])
})
return json
}
/**
......@@ -71,8 +80,8 @@ const parseQueryString = url => {
* @returns 包含状态
*/
const strExist = (array, str) => {
const exist = array.filter(arr => {
if (str.indexOf(arr) >= 0) return str;
const exist = array.filter((arr) => {
if (str.indexOf(arr) >= 0) return str
})
return exist.length > 0
}
......@@ -85,45 +94,61 @@ const strExist = (array, str) => {
* @returns
*/
const changeURLArg = (url, arg, arg_val) => {
var pattern = arg + '=([^&]*)';
var replaceText = arg + '=' + arg_val;
var pattern = arg + '=([^&]*)'
var replaceText = arg + '=' + arg_val
if (url.match(pattern)) {
var tmp = '/(' + arg + '=)([^&]*)/gi';
tmp = url.replace(eval(tmp), replaceText);
return tmp;
var tmp = '/(' + arg + '=)([^&]*)/gi'
tmp = url.replace(eval(tmp), replaceText)
return tmp
} else {
if (url.match('[\?]')) {
return url + '&' + replaceText;
if (url.match('[?]')) {
return url + '&' + replaceText
} else {
return url + '?' + replaceText;
return url + '?' + replaceText
}
}
return url + '\n' + arg + '\n' + arg_val;
return url + '\n' + arg + '\n' + arg_val
}
// 获取参数key/value值对
const getUrlParams = (url) => {
// 没有参数处理
if (url.split('?').length === 1) return false;
let arr = url.split('?');
let res = arr[1].split('&');
let items = {};
if (url.split('?').length === 1) return false
let arr = url.split('?')
let res = arr[1].split('&')
let items = {}
for (let i = 0; i < res.length; i++) {
let [key, value] = res[i].split('=');
items[key] = value;
let [key, value] = res[i].split('=')
items[key] = value
}
return items
}
// 格式化URL参数为字符串
const stringifyQuery = (params) => {
const queryString = [];
const queryString = []
Object.keys(params || {}).forEach((k) => {
queryString.push(k + '=' + params[k]);
});
queryString.push(k + '=' + params[k])
})
return '?' + queryString.join('&')
}
return '?' + queryString.join('&');
};
// 深克隆
const deepClone = (val) => {
if (val.constructor == Object) {
let obj = {},
keys = Object.keys(val)
for (let i = 0; i < keys.length; i++) {
obj[keys[i]] = deepClone(val[keys[i]])
}
return obj
} else if (Array.isArray(val)) {
return [].concat(val)
} else {
return val
}
}
export {
formatDate,
......@@ -134,4 +159,5 @@ export {
changeURLArg,
getUrlParams,
stringifyQuery,
};
deepClone,
}
......