Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Hooke
/
custom_form
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Graphs
Network
Create a new issue
Commits
Issue Boards
Authored by
hookehuyr
2023-04-04 11:12:49 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
0310115a36153fd03cf6465c80d8ced4eb2da2eb
0310115a
1 parent
ec1a2452
✨ feat(多选框控件): 样式和组件逻辑修改
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
202 additions
and
111 deletions
components.d.ts
src/components/CheckboxField/index.vue
src/hooks/useComponentType.js
src/pages/table/index.vue
src/utils/tools.js
components.d.ts
View file @
0310115
...
...
@@ -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'
]
...
...
src/components/CheckboxField/index.vue
View file @
0310115
<!--
* @Date: 2022-08-30 11:34:19
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-0
2-10 10:06:43
* @FilePath: /
data-table
/src/components/CheckboxField/index.vue
* @LastEditTime: 2023-0
4-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"> *</span
>
<
text v-if="item.component_props.required" style="color: red"> *</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">
(最多可选数: {{ 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"
: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 : ''"
:required="x.input_required" class="affix-input" />
</div>
</van-checkbox-group>
</template>
</van-field>
<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">
<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>
</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;
...
...
src/hooks/useComponentType.js
View file @
0310115
...
...
@@ -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
// }
...
...
src/pages/table/index.vue
View file @
0310115
<!--
* @Date: 2023-03-24 09:19:27
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-04-0
3 16:52:30
* @LastEditTime: 2023-04-0
4 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) => {
...
...
src/utils/tools.js
View file @
0310115
/*
* @Date: 2022-04-18 15:59:42
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-0
2-24 16:13:06
* @FilePath: /
data-table
/src/utils/tools.js
* @LastEditTime: 2023-0
4-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
,
}
...
...
Please
register
or
login
to post a comment