hookehuyr

fix 依赖文件

Showing 44 changed files with 5228 additions and 8 deletions
...@@ -7,9 +7,42 @@ export {} ...@@ -7,9 +7,42 @@ export {}
7 7
8 declare module '@vue/runtime-core' { 8 declare module '@vue/runtime-core' {
9 export interface GlobalComponents { 9 export interface GlobalComponents {
10 + AreaPickerField: typeof import('./src/components/AreaPickerField/index.vue')['default']
11 + ButtonField: typeof import('./src/components/ButtonField/index.vue')['default']
12 + CalendarField: typeof import('./src/components/CalendarField/index.vue')['default']
13 + CheckboxField: typeof import('./src/components/CheckboxField/index.vue')['default']
14 + ContactField: typeof import('./src/components/ContactField/index.vue')['default']
15 + DatePickerField: typeof import('./src/components/DatePickerField/index.vue')['default']
16 + DateTimePickerField: typeof import('./src/components/DateTimePickerField/index.vue')['default']
17 + DesField: typeof import('./src/components/DesField/index.vue')['default']
18 + DividerField: typeof import('./src/components/DividerField/index.vue')['default']
19 + EmailField: typeof import('./src/components/EmailField/index.vue')['default']
20 + FileUploaderField: typeof import('./src/components/FileUploaderField/index.vue')['default']
21 + GenderField: typeof import('./src/components/GenderField/index.vue')['default']
22 + IdentityField: typeof import('./src/components/IdentityField/index.vue')['default']
23 + ImageUploaderField: typeof import('./src/components/ImageUploaderField/index.vue')['default']
24 + MarqueeField: typeof import('./src/components/MarqueeField/index.vue')['default']
25 + MultiRuleField: typeof import('./src/components/MultiRuleField/index.vue')['default']
26 + NameField: typeof import('./src/components/NameField/index.vue')['default']
27 + NoteField: typeof import('./src/components/NoteField/index.vue')['default']
28 + NumberField: typeof import('./src/components/NumberField/index.vue')['default']
10 NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider'] 29 NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider']
11 NutDialog: typeof import('@nutui/nutui-taro')['Dialog'] 30 NutDialog: typeof import('@nutui/nutui-taro')['Dialog']
31 + NutInput: typeof import('@nutui/nutui-taro')['Input']
32 + NutNoticebar: typeof import('@nutui/nutui-taro')['Noticebar']
33 + PhoneField: typeof import('./src/components/PhoneField/index.vue')['default']
34 + PickerField: typeof import('./src/components/PickerField/index.vue')['default']
35 + RadioField: typeof import('./src/components/RadioField/index.vue')['default']
36 + RatePickerField: typeof import('./src/components/RatePickerField/index.vue')['default']
12 RouterLink: typeof import('vue-router')['RouterLink'] 37 RouterLink: typeof import('vue-router')['RouterLink']
13 RouterView: typeof import('vue-router')['RouterView'] 38 RouterView: typeof import('vue-router')['RouterView']
39 + RuleField: typeof import('./src/components/RuleField/index.vue')['default']
40 + SignField: typeof import('./src/components/SignField/index.vue')['default']
41 + TableField: typeof import('./src/components/TableField/index.vue')['default']
42 + Test: typeof import('./src/components/VideoField/test.vue')['default']
43 + TextareaField: typeof import('./src/components/TextareaField/index.vue')['default']
44 + TextField: typeof import('./src/components/TextField/index.vue')['default']
45 + TimePickerField: typeof import('./src/components/TimePickerField/index.vue')['default']
46 + VideoField: typeof import('./src/components/VideoField/index.vue')['default']
14 } 47 }
15 } 48 }
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
40 "@nutui/icons-vue-taro": "^0.0.9", 40 "@nutui/icons-vue-taro": "^0.0.9",
41 "@nutui/nutui-taro": "^4.0.0", 41 "@nutui/nutui-taro": "^4.0.0",
42 "@tarojs/components": "3.6.2", 42 "@tarojs/components": "3.6.2",
43 + "@tarojs/extend": "^3.6.2",
43 "@tarojs/helper": "3.6.2", 44 "@tarojs/helper": "3.6.2",
44 "@tarojs/plugin-framework-vue3": "3.6.2", 45 "@tarojs/plugin-framework-vue3": "3.6.2",
45 "@tarojs/plugin-html": "3.6.2", 46 "@tarojs/plugin-html": "3.6.2",
......
1 +<!--
2 + * @Date: 2022-08-30 14:32:11
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-14 14:37:45
5 + * @FilePath: /data-table/src/components/AreaPickerField/index.vue
6 + * @Description: 省市区选择控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="area-picker-field">
10 + <div class="label"><span v-if="item.component_props.required">&nbsp;*</span>{{ item.component_props.label }}</div>
11 + <van-field
12 + name="ignore"
13 + v-model="fieldValue"
14 + is-link
15 + readonly
16 + :required="item.component_props.required"
17 + placeholder="请选择省市区"
18 + :rules="item.rules"
19 + @click="showPicker = true"
20 + :border="show_address ? true : false"
21 + />
22 + <van-field
23 + v-if="show_address"
24 + name="ignore"
25 + v-model="address"
26 + placeholder="请填写详细地址"
27 + @blur="onBlur"
28 + :rules="item.rules"
29 + :border="false"
30 + />
31 + <!-- <div
32 + v-if="show_empty"
33 + class="van-field__error-message"
34 + style="padding: 0 1rem 1rem 1rem"
35 + >
36 + 地址不能为空
37 + </div> -->
38 + <van-divider />
39 +
40 + <van-popup v-model:show="showPicker" position="bottom">
41 + <van-area
42 + v-model="item.city_code"
43 + title=""
44 + :area-list="areaList"
45 + @confirm="onConfirm"
46 + @cancel="showPicker = false"
47 + />
48 + </van-popup>
49 + </div>
50 +</template>
51 +
52 +<script setup>
53 +import { areaList } from "@vant/area-data";
54 +
55 +const props = defineProps({
56 + item: Object,
57 +});
58 +// 隐藏显示
59 +const HideShow = computed(() => {
60 + return !props.item.component_props.disabled
61 +})
62 +const emit = defineEmits(["active"]);
63 +const show_empty = ref(false);
64 +
65 +const show_address = ref(!props.item.component_props.no_street)
66 +
67 +const address = ref("");
68 +const city_code = ref("");
69 +const showPicker = ref(false);
70 +let fieldValue = ref("");
71 +
72 +const onConfirm = ({ selectedOptions }) => {
73 + fieldValue.value = selectedOptions.map((option) => option.text).join(" ");
74 + city_code.value = selectedOptions[2]?.value;
75 + props.item.value = {
76 + key: "area_picker",
77 + filed_name: props.item.key,
78 + value: {
79 + address: fieldValue.value + ' ' + address.value,
80 + city_code: city_code.value
81 + },
82 + };
83 + emit("active", props.item.value);
84 + showPicker.value = false;
85 +};
86 +
87 +const onBlur = () => {
88 + props.item.value = {
89 + key: "area_picker",
90 + filed_name: props.item.key,
91 + value: {
92 + address: fieldValue.value + ' ' + address.value,
93 + city_code: city_code.value
94 + },
95 + };
96 + emit("active", props.item.value);
97 +}
98 +
99 +// 校验模块
100 +const validAreaPicker = () => {
101 + // 必填项
102 + if (props.item.component_props.required && !fieldValue.value) {
103 + show_empty.value = true;
104 + } else if (props.item.component_props.required && !address.value) {
105 + show_empty.value = true;
106 + } else {
107 + show_empty.value = false;
108 + }
109 + return !show_empty.value;
110 +};
111 +
112 +defineExpose({ validAreaPicker });
113 +</script>
114 +
115 +<style lang="less" scoped>
116 +.area-picker-field {
117 + .label {
118 + padding: 1rem 1rem 0 1rem;
119 + font-size: 0.9rem;
120 + font-weight: bold;
121 +
122 + span {
123 + color: red;
124 + }
125 + }
126 +}
127 +
128 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-11-22 14:23:49
5 + * @FilePath: /data-table/src/components/DividerField/index.vue
6 + * @Description: 按钮组件
7 +-->
8 +<template>
9 + <div class="button-field-page">
10 + <van-row justify="center" gutter="20">
11 + <van-col v-for="item in props.item.component_props.config" key="index">
12 + <van-button
13 + @click="handleButton(item)"
14 + :icon="iconType(item)"
15 + :color="item.background ? item.background : backgroundColor"
16 + >{{ item.text }}
17 + </van-button>
18 + </van-col>
19 + </van-row>
20 + </div>
21 + <van-overlay :show="show" @click="onClose">
22 + <div class="wrapper">
23 + <div class="block">
24 + <van-image width="100%" fit="cover" :src="qr_url" />
25 + </div>
26 + </div>
27 + </van-overlay>
28 +</template>
29 +
30 +<script setup>
31 +import { styleColor } from "@/constant.js";
32 +
33 +const props = defineProps({
34 + item: Object,
35 +});
36 +
37 +const show = ref(false);
38 +const backgroundColor = styleColor.baseColor;
39 +
40 +const iconType = (item) => {
41 + if (item.type === "tel") return "phone-o";
42 + if (item.type === "link") return "link-o";
43 + if (item.type === "qr") return "qr";
44 +};
45 +
46 +const qr_url = ref("");
47 +const handleButton = ({ type, content }) => {
48 + if (type === "tel") {
49 + location.href = "tel://" + content;
50 + }
51 + if (type === "link") {
52 + location.href = content;
53 + }
54 + if (type === "qr") {
55 + show.value = true;
56 + qr_url.value = content;
57 + }
58 +};
59 +
60 +const onClose = () => {
61 + show.value = false;
62 +};
63 +
64 +onMounted(() => {});
65 +</script>
66 +
67 +<style lang="less" scoped>
68 +.button-field-page {
69 +}
70 +
71 +.wrapper {
72 + display: flex;
73 + align-items: center;
74 + justify-content: center;
75 + height: 100%;
76 +}
77 +
78 +.block {
79 + width: 10rem;
80 + height: 10rem;
81 + background-color: #fff;
82 +}
83 +</style>
1 +<!--
2 + * @Date: 2022-09-14 11:00:01
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-10 10:06:27
5 + * @FilePath: /data-table/src/components/CalendarField/index.vue
6 + * @Description: 日历选择控件
7 +-->
8 +<template>
9 + <div class="calendar-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-field
15 + v-model="item.value"
16 + is-link
17 + readonly
18 + :name="item.key"
19 + :required="item.component_props.required"
20 + :placeholder="item.component_props.placeholder"
21 + :rules="item.rules"
22 + @click="show = true"
23 + />
24 + <van-calendar
25 + v-model:show="show"
26 + :type="item.component_props.type"
27 + :max-range="item.component_props.max_range"
28 + :min-date="item.component_props.min_date"
29 + :max-date="item.component_props.max_date"
30 + :formatter="formatter"
31 + first-day-of-week="1"
32 + @month-show="onMonthShow"
33 + @confirm="onConfirm"
34 + allow-same-day
35 + />
36 + </div>
37 +</template>
38 +
39 +<script setup>
40 +const props = defineProps({
41 + item: Object,
42 +});
43 +
44 +const show = ref(false);
45 +
46 +const formatDate = (date) =>
47 + `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
48 +const onConfirm = (value) => {
49 + console.warn(props.item.component_props.type);
50 + show.value = false;
51 + if (props.item.component_props.type === "range") {
52 + // 日期区间
53 + const [start, end] = value;
54 + props.item.value = `${formatDate(start)} ~ ${formatDate(end)}`;
55 + } else if (props.item.component_props.type === "multiple") {
56 + // 多个日期
57 + const arr = [];
58 + value.forEach((element) => {
59 + arr.push(formatDate(element));
60 + });
61 + props.item.value = arr.join(",");
62 + } else {
63 + props.item.value = formatDate(value);
64 + }
65 +};
66 +
67 +// 每一格内容格式化
68 +const formatter = (day) => {
69 + const month = day.date.getMonth() + 1;
70 + const date = day.date.getDate();
71 + const year = day.date.getFullYear();
72 +
73 + if (month === 5) {
74 + if (date === 1) {
75 + day.topInfo = "劳动节";
76 + } else if (date === 4) {
77 + day.topInfo = "青年节";
78 + } else if (date === 11) {
79 + day.text = "今天";
80 + }
81 + }
82 +
83 + if (month === 10) {
84 + if (date === 1) {
85 + day.topInfo = "国庆节";
86 + day.type = "disabled";
87 + }
88 + }
89 +
90 + if (day.type === "start") {
91 + day.bottomInfo = "开始";
92 + } else if (day.type === "end") {
93 + day.bottomInfo = "结束";
94 + }
95 +
96 + return day;
97 +};
98 +
99 +const onMonthShow = ({ date, title }) => {
100 + // console.warn(date);
101 + // console.warn(title);
102 + if (title === "2022年12月") {
103 + }
104 +};
105 +</script>
106 +
107 +<style lang="less" scoped>
108 +.calendar-page {
109 + .label {
110 + padding: 1rem 1rem 0 1rem;
111 + font-size: 0.9rem;
112 + font-weight: bold;
113 +
114 + span {
115 + color: red;
116 + }
117 + }
118 +}
119 +</style>
1 +<!--
2 + * @Date: 2022-08-30 11:34:19
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-10 10:06:43
5 + * @FilePath: /data-table/src/components/CheckboxField/index.vue
6 + * @Description: 多项选择控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="checkbox-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required" style="color: red">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + <span v-if="item.component_props.max" style="color: gray">
14 + (最多可选数:&nbsp;{{ item.component_props.max }})
15 + </span>
16 + </div>
17 + <div v-if="item.component_props.note" class="note" v-html="item.component_props.note" />
18 + <van-field :rules="item.rules" :border="false">
19 + <template #input>
20 + <van-checkbox-group v-model="checkbox_value" :direction="item.component_props.direction"
21 + :max="item.component_props.max" style="width: 100%">
22 + <div v-for="x in item.component_props.options" :key="x.title" class="checkbox-wrapper">
23 + <van-checkbox @click="onClick(x)" :name="x.title" icon-size="1rem" shape="square"
24 + :checked-color="themeVars.radioColor" style="margin-bottom: 0.25rem">{{ x.title }}</van-checkbox>
25 + <van-field v-if="checkbox_value.includes(x.value) && x.is_input" @blur="onBlur(x)" v-model="x.affix"
26 + label=" " label-width="5px" :placeholder="x.input_placeholder" :rules="x.input_required ? rules : ''"
27 + :required="x.input_required" class="affix-input" />
28 + </div>
29 + </van-checkbox-group>
30 + </template>
31 + </van-field>
32 + </div>
33 +</template>
34 +
35 +<script setup>
36 +import { styleColor } from "@/constant.js";
37 +
38 +const props = defineProps({
39 + item: Object,
40 +});
41 +// TAG: 自定义主题颜色
42 +const themeVars = {
43 + radioColor: styleColor.baseColor,
44 +};
45 +onMounted(() => {
46 + // 默认值为数组
47 + props.item.value = props.item.component_props.default;
48 +});
49 +// 隐藏显示
50 +const HideShow = computed(() => {
51 + return !props.item.component_props.disabled
52 +})
53 +
54 +// TODO: 等待数据结构更新,看看怎么判断必填
55 +// 校验函数返回 true 表示校验通过,false 表示不通过
56 +const validator = (val) => {
57 + if (!val) {
58 + return false;
59 + } else {
60 + return true;
61 + }
62 +};
63 +// 错误提示文案
64 +const validatorMessage = (val, rule) => {
65 + if (!val) {
66 + return "补充信息不能为空";
67 + }
68 +};
69 +const rules = [{ validator, message: validatorMessage }];
70 +
71 +const emit = defineEmits(["active"]);
72 +const checkbox_value = ref(props.item.component_props.default);
73 +const affix_value = ref({});
74 +
75 +const onClick = (item) => {
76 + item.checked = !item.checked;
77 + handleEmit(item)
78 +}
79 +const onBlur = (item) => {
80 + handleEmit(item)
81 +}
82 +const handleEmit = (item) => {
83 + // 选中状态添加属性
84 + if (item.checked) {
85 + affix_value.value[item.value] = item.affix ? `${item.title}: ${item.affix}` : '';
86 + } else {
87 + // 为选中删除属性
88 + delete affix_value.value[item.value]
89 + }
90 + // 发送自定义数据结构
91 + props.item.value = { key: props.item.key, value: checkbox_value.value, affix: affix_value.value, type: "checkbox" };
92 + emit("active", props.item.value);
93 +}
94 +onMounted(() => {
95 + // 发送自定义数据结构
96 + props.item.value = { key: props.item.key, value: checkbox_value.value, affix: affix_value.value, type: "checkbox" };
97 + emit("active", props.item.value);
98 +})
99 +</script>
100 +
101 +<style lang="less" scoped>
102 +.checkbox-field-page {
103 + .label {
104 + padding: 1rem 1rem 0 1rem;
105 + font-size: 0.9rem;
106 + font-weight: bold;
107 + }
108 +
109 + .note {
110 + font-size: 0.9rem;
111 + margin-left: 1rem;
112 + color: gray;
113 + padding-bottom: 0.5rem;
114 + white-space: pre-wrap;
115 + }
116 +
117 + .checkbox-wrapper {
118 + border: 1px solid #eaeaea;
119 + border-radius: 0.25rem;
120 + padding: 0.25rem 0.5rem;
121 + margin-bottom: 0.25rem;
122 + }
123 + .affix-input {
124 + border: 1px solid #eaeaea;
125 + border-radius: 0.25rem;
126 + padding: 0.25rem 0.5rem;
127 + margin-top: 0.5rem;
128 + margin-bottom: 0.25rem;
129 + }
130 +}
131 +
132 +:deep(.van-checkbox) {
133 + // border: 1px solid #eaeaea;
134 + // border-radius: 0.25rem;
135 + // padding: 0.25rem 0.5rem;
136 +}
137 +</style>
1 +<!--
2 + * @Date: 2022-11-23 14:41:53
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-01-06 19:04:00
5 + * @FilePath: /data-table/src/components/ContactField/index.vue
6 + * @Description: 联系我们控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="contact-field-page">
10 + <van-popover v-model:show="showPopover" placement="left">
11 + <div class="contact-content">
12 + <div class="text-tel">电话联系</div>
13 + <div>
14 + <a
15 + :style="{ color: styleColor.baseColor }"
16 + :href="`tel:${item.component_props.tel}`"
17 + >
18 + {{ item.component_props.tel }}
19 + </a>
20 + </div>
21 + <div v-if="item.component_props.image_url">
22 + <div class="text-qr_code">微信联系</div>
23 + <van-image width="100" height="100" :src="item.component_props.image_url" />
24 + </div>
25 + </div>
26 + <template #reference>
27 + <div class="wrapper">
28 + <van-icon name="phone-o" size="2rem" :color="styleColor.baseColor" />
29 + </div>
30 + </template>
31 + </van-popover>
32 + </div>
33 +</template>
34 +
35 +<script setup>
36 +import { ref } from "vue";
37 +import { useRoute, useRouter } from "vue-router";
38 +import { styleColor } from "@/constant.js";
39 +
40 +const props = defineProps({
41 + item: Object,
42 +});
43 +// 隐藏显示
44 +const HideShow = computed(() => {
45 + return !props.item.component_props.disabled
46 +})
47 +const showPopover = ref(false);
48 +</script>
49 +
50 +<style lang="less" scoped>
51 +.contact-field-page {
52 + position: fixed;
53 + bottom: 10rem;
54 + right: 0.5rem;
55 + // height: 100%;
56 + z-index: 9;
57 + .wrapper {
58 + background: white;
59 + width: 3rem;
60 + height: 3rem;
61 + border-radius: 50%;
62 + text-align: center;
63 + box-shadow: 0rem -0.17rem 0.67rem 0.08rem rgba(0, 0, 0, 0.05);
64 + :deep(.van-icon) {
65 + line-height: 1.5;
66 + }
67 + }
68 +}
69 +
70 +.contact-content {
71 + padding: 1rem 0.85rem;
72 + text-align: center;
73 + .text-tel {
74 + color: gray;
75 + font-size: 0.9rem;
76 + margin-bottom: 0.5rem;
77 + }
78 + .text-qr_code {
79 + color: gray;
80 + font-size: 0.9rem;
81 + margin-bottom: 0.5rem;
82 + margin-top: 0.5rem;
83 + }
84 +}
85 +</style>
1 +<!--
2 + * @Date: 2022-08-31 11:45:30
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-10 10:07:31
5 + * @FilePath: /data-table/src/components/DatePickerField/index.vue
6 + * @Description: 日期选择组件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="date-picker-field">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-field
15 + v-model="item.value"
16 + is-link
17 + readonly
18 + :name="item.key"
19 + :required="item.component_props.required"
20 + :disabled="item.component_props.readonly"
21 + :placeholder="item.component_props.placeholder ? item.component_props.placeholder : '请选择日期'"
22 + :rules="rules"
23 + @click="onTap"
24 + :border="false"
25 + />
26 + <van-popup v-model:show="showPicker" position="bottom">
27 + <van-date-picker
28 + v-model="currentDate"
29 + title="日期选择"
30 + :min-date="minDate"
31 + :max-date="maxDate"
32 + :columns-type="columns_type"
33 + @confirm="onConfirm"
34 + @cancel="showPicker = false"
35 + />
36 + </van-popup>
37 + </div>
38 +</template>
39 +
40 +<script setup>
41 +import dayjs from "dayjs";
42 +
43 +const props = defineProps({
44 + item: Object,
45 +});
46 +
47 +// 隐藏显示
48 +const HideShow = computed(() => {
49 + return !props.item.component_props.disabled
50 +})
51 +const showPicker = ref(false);
52 +const currentDate = ref([]);
53 +const readonly = props.item.component_props.readonly;
54 +
55 +const onTap = () => {
56 + if (readonly) return false; // 如果为只读,不能设置
57 + showPicker.value = true
58 +}
59 +const onConfirm = ({ selectedValues, selectedOptions }) => {
60 + props.item.value = selectedValues.join("-");
61 + showPicker.value = false;
62 +};
63 +
64 +const columns_type = ref([]);
65 +const date_format = props.item.component_props.data_dateformat; // YYYY-MM=年月,YYYY-MM-DD=年月日
66 +// 数字前面补位
67 +const formatZero = (num, len) => {
68 + if (String(num).length > len) {
69 + return num;
70 + }
71 + return (Array(len).join(0) + num).slice(-len)
72 +}
73 +
74 +const minDate = ref()
75 +const maxDate = ref()
76 +
77 +onMounted(() => {
78 + // 根据默认值时间调整显示
79 + currentDate.value = props.item.component_props.default ? props.item.component_props.default.split("-") : props.item.value.split("-");
80 + let Year = '';
81 + let Month = '';
82 + let Day = '';
83 + if (!props.item.component_props.default) {
84 + Year = String(dayjs().year());
85 + Month = formatZero(dayjs().month(), 2);
86 + Day = formatZero(dayjs().date(), 2);
87 + } else {
88 + Year = currentDate.value[0];
89 + Month = formatZero(currentDate.value[1], 2);
90 + Day = formatZero(currentDate.value[2], 2);
91 + }
92 + switch (date_format) {
93 + case "YYYY-MM":
94 + columns_type.value = ['year', 'month']
95 + // 设置默认值
96 + currentDate.value = [Year, Month];
97 + break;
98 + case "YYYY-MM-DD":
99 + columns_type.value = ['year', 'month', 'day']
100 + // 设置默认值
101 + currentDate.value = [Year, Month, Day];
102 + break;
103 + }
104 + // 设置默认最大最小日期
105 + if (data_minvalue.length) {
106 + const min = data_minvalue.split("-")
107 + minDate.value = new Date(+min[0], +min[1] - 1, +min[2])
108 + }
109 + if (data_maxvalue.length) {
110 + const max = data_maxvalue.split("-")
111 + maxDate.value = new Date(+max[0], +max[1] - 1, +max[2])
112 + }
113 +});
114 +
115 +const required = props.item.component_props.required;
116 +const data_minvalue = props.item.component_props.data_minvalue;
117 +const data_maxvalue = props.item.component_props.data_maxvalue;
118 +const validator = (val) => {
119 + if (required && !val) {
120 + return false;
121 + } else if (val && data_minvalue && val < data_minvalue) {
122 + return false;
123 + } else if (val && data_maxvalue && val > data_maxvalue) {
124 + return false;
125 + } else {
126 + return true;
127 + }
128 +};
129 +// 错误提示文案
130 +const validatorMessage = (val, rule) => {
131 + if (required && !val) {
132 + return "必填项不能为空";
133 + } else if (val && data_minvalue && val < data_minvalue) {
134 + return "最小可选:" + data_minvalue;
135 + } else if (val && data_maxvalue && val > data_maxvalue) {
136 + return "最大可选:" + data_maxvalue;
137 + }
138 +};
139 +const rules = [{ validator, message: validatorMessage }];
140 +</script>
141 +
142 +<style lang="less" scoped>
143 +.date-picker-field {
144 + margin: 1rem;
145 + .label {
146 + // padding: 1rem 1rem 0 1rem;
147 + font-size: 0.9rem;
148 + font-weight: bold;
149 +
150 + span {
151 + color: red;
152 + }
153 + }
154 + :deep(.van-icon) { // 处理正式服务器上箭头上下位移问题
155 + font-size: var(--van-cell-icon-size);
156 + line-height: var(--van-cell-line-height);
157 + }
158 +}
159 +
160 +:deep(.van-cell--clickable) {
161 + border: 1px solid #eaeaea;
162 + border-radius: 0.25rem;
163 + padding: 0.25rem 0.5rem;
164 + margin-top: 0.5rem;
165 + input {
166 + color: #323233;
167 + }
168 +}
169 +</style>
1 +<!--
2 + * @Date: 2022-09-08 15:02:45
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-07 16:46:30
5 + * @FilePath: /data-table/src/components/DateTimePickerField/index.vue
6 + * @Description: 日期时间选择器
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="datetime-picker">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-field
15 + v-model="item.value"
16 + is-link
17 + readonly
18 + :name="item.key"
19 + :required="item.component_props.required"
20 + :disabled="item.component_props.readonly"
21 + :placeholder="item.component_props.placeholder ? item.component_props.placeholder : '请选择日期时间'"
22 + :rules="rules"
23 + @click="onTap"
24 + :border="false"
25 + />
26 + <van-popup v-model:show="showPicker" position="bottom">
27 + <van-picker-group
28 + title="请选择日期时间"
29 + :tabs="['选择日期', '选择时间']"
30 + @confirm="onConfirm"
31 + @cancel="onCancel"
32 + >
33 + <van-date-picker v-model="currentDate" :min-date="minDate" :max-date="maxDate" :columns-type="columns_date_type" />
34 + <van-time-picker v-model="currentTime" :columns-type="columns_time_type" />
35 + </van-picker-group>
36 + </van-popup>
37 + </div>
38 +</template>
39 +
40 +<script setup>
41 +import { showToast } from "vant";
42 +import dayjs from "dayjs";
43 +
44 +const props = defineProps({
45 + item: Object,
46 +});
47 +// 隐藏显示
48 +const HideShow = computed(() => {
49 + return !props.item.component_props.disabled
50 +})
51 +const showPicker = ref(false);
52 +const readonly = props.item.component_props.readonly;
53 +
54 +const onTap = () => {
55 + if (readonly) return false; // 如果为只读,不能设置
56 + showPicker.value = true
57 +}
58 +const currentDate = ref([]);
59 +const currentTime = ref([]);
60 +
61 +const onConfirm = () => {
62 + props.item.value = `${currentDate.value.join("-")} ${currentTime.value.join(":")}`;
63 + showPicker.value = false;
64 +};
65 +const onCancel = () => {
66 + showPicker.value = false;
67 +};
68 +
69 +const columns_date_type = ref([]);
70 +const columns_time_type = ref([]);
71 +const date_format = props.item.component_props.data_dateformat;
72 +// 数字前面补位
73 +const formatZero = (num, len) => {
74 + if (String(num).length > len) {
75 + return num;
76 + }
77 + return (Array(len).join(0) + num).slice(-len)
78 +}
79 +
80 +const minDate = ref()
81 +const maxDate = ref()
82 +
83 +onMounted(() => {
84 + // 根据默认值时间调整显示
85 + const datetime = props.item.component_props.default ? props.item.component_props.default.split(" ") : props.item.value.split(" ");
86 + currentDate.value = datetime[0]?.split("-");
87 + currentTime.value = datetime[1]?.split(":");
88 + // YYYY=年,YYYY-MM=年月,YYYY-MM-DD=年月日,YYYY-MM-DD HH=年月日时,YYYY-MM-DD HH:mm=年月日时分,YYYY-MM-DD HH:mm:ss=年月日时分秒
89 + let Year = '';
90 + let Month = '';
91 + let Day = '';
92 + if (!props.item.component_props.default) {
93 + Year = String(dayjs().year());
94 + Month = formatZero(dayjs().month(), 2);
95 + Day = formatZero(dayjs().date(), 2);
96 + } else {
97 + Year = currentDate.value[0];
98 + Month = formatZero(currentDate.value[1], 2);
99 + Day = formatZero(currentDate.value[2], 2);
100 + }
101 + let Hour = ''
102 + let Minute = ''
103 + let Second = ''
104 + if (!props.item.component_props.default) {
105 + Hour = String(dayjs().hour());
106 + Minute = String(dayjs().minute());
107 + Second = String(dayjs().second());
108 + } else {
109 + Hour = currentTime.value[0];
110 + Minute = currentTime.value[1];
111 + Second = currentTime.value[2];
112 + }
113 + switch (date_format) {
114 + case "YYYY":
115 + columns_date_type.value = ['year']
116 + // 设置默认值
117 + currentDate.value = [Year];
118 + break;
119 + case "YYYY-MM":
120 + columns_date_type.value = ['year', 'month']
121 + // 设置默认值
122 + currentDate.value = [Year, Month];
123 + break;
124 + case "YYYY-MM-DD":
125 + columns_date_type.value = ['year', 'month', 'day']
126 + // 设置默认值
127 + currentDate.value = [Year, Month, Day];
128 + break;
129 + case "YYYY-MM-DD HH":
130 + columns_date_type.value = ['year', 'month', 'day']
131 + columns_time_type.value = ['hour']
132 + // 设置默认值
133 + currentDate.value = [Year, Month, Day];
134 + currentTime.value = [Hour];
135 + break;
136 + case "YYYY-MM-DD HH:mm":
137 + columns_date_type.value = ['year', 'month', 'day']
138 + columns_time_type.value = ['hour', 'minute']
139 + // 设置默认值
140 + currentDate.value = [Year, Month, Day];
141 + currentTime.value = [Hour, Minute];
142 + break;
143 + case "YYYY-MM-DD HH:mm:ss":
144 + columns_date_type.value = ['year', 'month', 'day']
145 + columns_time_type.value = ['hour', 'minute', 'second']
146 + // 设置默认值
147 + currentDate.value = [Year, Month, Day];
148 + currentTime.value = [Hour, Minute, Second];
149 + break;
150 + }
151 + // 设置默认最大最小日期
152 + if (data_minvalue.split(" ")[0].length) {
153 + const min = data_minvalue.split(" ")[0].split("-")
154 + minDate.value = new Date(+min[0], +min[1] - 1, +min[2])
155 + }
156 + if (data_maxvalue.split(" ")[0].length) {
157 + const max = data_maxvalue.split(" ")[0].split("-")
158 + maxDate.value = new Date(+max[0], +max[1] - 1, +max[2])
159 + }
160 +});
161 +
162 +const required = props.item.component_props.required;
163 +const data_minvalue = props.item.component_props.data_minvalue;
164 +const data_maxvalue = props.item.component_props.data_maxvalue;
165 +const validator = (val) => {
166 + if (required && !val) {
167 + return false;
168 + } else if (val && data_minvalue && val < data_minvalue) {
169 + return false;
170 + } else if (val && data_maxvalue && val > data_maxvalue) {
171 + return false;
172 + } else {
173 + return true;
174 + }
175 +};
176 +// 错误提示文案
177 +const validatorMessage = (val, rule) => {
178 + if (required && !val) {
179 + return "必填项不能为空";
180 + } else if (val && data_minvalue && val < data_minvalue) {
181 + return "最小可选:" + data_minvalue;
182 + } else if (val && data_maxvalue && val > data_maxvalue) {
183 + return "最大可选:" + data_maxvalue;
184 + }
185 +};
186 +const rules = [{ validator, message: validatorMessage }];
187 +</script>
188 +
189 +<style lang="less" scoped>
190 +.datetime-picker {
191 + margin: 1rem;
192 + .label {
193 + // padding: 1rem 1rem 0 1rem;
194 + font-size: 0.9rem;
195 + font-weight: bold;
196 +
197 + span {
198 + color: red;
199 + }
200 + }
201 + :deep(.van-icon) { // 处理正式服务器上箭头上下位移问题
202 + font-size: var(--van-cell-icon-size);
203 + line-height: var(--van-cell-line-height);
204 + }
205 +}
206 +
207 +:deep(.van-cell--clickable) {
208 + border: 1px solid #eaeaea;
209 + border-radius: 0.25rem;
210 + padding: 0.25rem 0.5rem;
211 + margin-top: 0.5rem;
212 + input {
213 + color: #323233;
214 + }
215 +}
216 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-12-08 14:52:48
5 + * @FilePath: /data-table/src/components/DesField/index.vue
6 + * @Description: 描述文本控件
7 +-->
8 +<template>
9 + <div class="des-field-page">
10 + <div class="label">{{ item.component_props.label }}</div>
11 + <van-field
12 + v-model="item.component_props.desc"
13 + name="ignore"
14 + :readonly="true"
15 + :border="false"
16 + />
17 + </div>
18 + <van-divider />
19 +</template>
20 +
21 +<script setup>
22 +const props = defineProps({
23 + item: Object,
24 +});
25 +</script>
26 +
27 +<style lang="less" scoped>
28 +.des-field-page {
29 + .label {
30 + padding: 1rem 1rem 0 1rem;
31 + font-size: 0.9rem;
32 + font-weight: bold;
33 + span {
34 + color: red;
35 + }
36 + }
37 +}
38 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-11-22 14:23:49
5 + * @FilePath: /data-table/src/components/DividerField/index.vue
6 + * @Description: 分隔线组件
7 +-->
8 +<template>
9 + <div class="divider-field-page">
10 + <van-divider :style="styleObj">
11 + {{ item.component_props.content }}
12 + </van-divider>
13 + </div>
14 +</template>
15 +
16 +<script setup>
17 +const props = defineProps({
18 + item: Object,
19 +});
20 +
21 +const styleObj = ref({});
22 +onMounted(() => {
23 + styleObj.value = {
24 + color: props.item.component_props.color,
25 + borderColor: props.item.component_props.color,
26 + padding: props.item.component_props.padding,
27 + };
28 +});
29 +</script>
30 +
31 +<style lang="less" scoped>
32 +.divider-field-page {
33 +}
34 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-01-18 15:58:11
5 + * @FilePath: /data-table/src/components/EmailField/index.vue
6 + * @Description: 邮箱输入框
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="text-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-field
15 + v-model="item.value"
16 + :name="item.name"
17 + type="email"
18 + :placeholder="item.component_props.placeholder"
19 + :rules="rules"
20 + :required="item.component_props.required"
21 + :disabled="item.component_props.disabled"
22 + :readonly="item.component_props.readonly"
23 + clearable
24 + />
25 + </div>
26 +</template>
27 +
28 +<script setup>
29 +const props = defineProps({
30 + item: Object,
31 +});
32 +// 隐藏显示
33 +const HideShow = computed(() => {
34 + return !props.item.component_props.disabled
35 +})
36 +const required = props.item.component_props.required;
37 +const validator = (val) => {
38 + if (required && !val) {
39 + return false;
40 + } else if (val && !/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/.test(val)) {
41 + return false;
42 + } else {
43 + return true;
44 + }
45 +};
46 +// 错误提示文案
47 +const validatorMessage = (val, rule) => {
48 + if (required && !val) {
49 + return "必填项不能为空";
50 + } else if (val && !/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/.test(val)) { // 小于最小值
51 + return "请输入正确邮箱";
52 + }
53 +};
54 +const rules = [{ validator, message: validatorMessage }];
55 +</script>
56 +
57 +<style lang="less" scoped>
58 +.text-field-page {
59 + .label {
60 + padding: 1rem 1rem 0 1rem;
61 + font-size: 0.9rem;
62 + font-weight: bold;
63 + span {
64 + color: red;
65 + }
66 + }
67 +}
68 +
69 +:deep(.van-field__body) {
70 + border: 1px solid #eaeaea;
71 + border-radius: 0.25rem;
72 + padding: 0.25rem 0.5rem;
73 +}
74 +</style>
1 +<!--
2 + * @Date: 2022-08-31 16:16:49
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-10 11:17:21
5 + * @FilePath: /data-table/src/components/FileUploaderField/index.vue
6 + * @Description: 文件上传控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="file-uploader-field">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <div
15 + v-if="item.component_props.note"
16 + v-html="item.component_props.note"
17 + style="font-size: 0.9rem; margin-left: 1rem; color: gray; padding-bottom: 0.5rem; padding-top: 0.25rem; white-space: pre-wrap;"
18 + />
19 + <div>
20 + <p
21 + v-for="(file, index) in fileList"
22 + :key="index"
23 + style="padding-left: 1rem; margin-bottom: 0.5rem"
24 + >
25 + <p style="font-size: 1rem; word-break: break-all; margin-right: 0.75rem;">
26 + <span>{{ index + 1 }}.&nbsp;{{ file.filename }}&nbsp;&nbsp;{{ (file.size / 1024 / 1024).toFixed(2) }}MB</span>
27 + &nbsp;&nbsp;
28 + <span style="color: #e32525; font-size: 0.85rem" @click="beforeDelete(file)">移除</span>
29 + </p>
30 + </p>
31 + </div>
32 + <div style="padding: 1rem">
33 + <van-uploader
34 + :name="item.name"
35 + upload-icon="add"
36 + accept="*"
37 + :before-read="beforeRead"
38 + :after-read="afterRead"
39 + :before-delete="beforeDelete"
40 + :multiple="item.component_props.max_size > 1"
41 + >
42 + <van-button icon="plus" type="primary">上传文件</van-button>
43 + </van-uploader>
44 + </div>
45 + <!-- <div class="type-text">上传格式:{{ type_text }}</div> -->
46 + <div
47 + v-if="show_empty"
48 + class="van-field__error-message"
49 + style="padding: 0 1rem 1rem 1rem"
50 + >
51 + 文件上传不能为空
52 + </div>
53 + <van-divider />
54 + </div>
55 +
56 + <van-overlay :show="loading">
57 + <div class="wrapper" @click.stop>
58 + <van-loading vertical color="#FFFFFF">上传中...</van-loading>
59 + </div>
60 + </van-overlay>
61 +</template>
62 +
63 +<script setup>
64 +/**
65 + * 文件上传
66 + * @param name[String] 组件名称
67 + * @param file_type[Array] 文件上传类型
68 + * @param multiple[Boolean] 文件多选
69 + */
70 +import { showSuccessToast, showFailToast, showToast } from "vant";
71 +import _ from "lodash";
72 +import { v4 as uuidv4 } from "uuid";
73 +import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from "@/api/common";
74 +import BMF from "browser-md5-file";
75 +import { useRoute } from "vue-router";
76 +import axios from "axios";
77 +import { getEtag } from "@/utils/qetag.js"; // 生成hash值
78 +
79 +const $route = useRoute();
80 +const props = defineProps({
81 + item: Object,
82 +});
83 +// 隐藏显示
84 +const HideShow = computed(() => {
85 + return !props.item.component_props.disabled
86 +})
87 +const emit = defineEmits(["active"]);
88 +const show_empty = ref(false);
89 +
90 +// 文件类型中文页面显示
91 +const type_text = computed(() => {
92 + return props.item.component_props.file_type;
93 +});
94 +// 上传文件集合
95 +const fileList = ref([
96 + // { url: "https://fastly.jsdelivr.net/npm/@vant/assets/leaf.jpeg" },
97 + // Uploader 根据文件后缀来判断是否为文件文件
98 + // 如果文件 URL 中不包含类型信息,可以添加 isImage 标记来声明
99 + // { url: 'https://cloud-image', isImage: true },
100 +]);
101 +
102 +// 上传前置处理
103 +const beforeRead = (file) => {
104 + // TODO: 需要file_type集合
105 + // 类型限制
106 + // const file_types = _.map(
107 + // props.item.component_props.file_type.split("/"),
108 + // (item) => `video/${item}`
109 + // );
110 + let flag = true;
111 + if (file.length + 1 > props.item.component_props.max_count) {
112 + // 数量限制
113 + flag = false;
114 + showToast(`最大上传数量为${props.item.component_props.max_count}个`);
115 + }
116 + if (fileList.value.length + 1 > props.item.component_props.max_count) {
117 + // 数量限制
118 + flag = false;
119 + showToast(`最大上传数量为${props.item.component_props.max_count}个`);
120 + }
121 + if ((file.size / 1024 / 1024).toFixed(2) > props.item.component_props.max_size) {
122 + // 体积限制
123 + flag = false;
124 + showToast(
125 + `最大文件体积为${props.item.component_props.max_size}MB`
126 + );
127 + }
128 + // if (_.isArray(file)) {
129 + // // 多张文件
130 + // const types = _.difference(_.uniq(_.map(file, (item) => item.type)), file_types); // 数组返回不能上传的类型
131 + // if (types.length) {
132 + // flag = false;
133 + // showFailToast("请上传指定格式文件");
134 + // }
135 + // } else {
136 + // if (!_.includes(file_types, file.type)) {
137 + // showFailToast("请上传指定格式文件");
138 + // flag = false;
139 + // }
140 + // }
141 + return flag;
142 +};
143 +
144 +// 文件读取完成后的回调函数
145 +const afterRead = async (files) => {
146 + if (Array.isArray(files)) {
147 + // 多张文件上传files是一个数组
148 + muliUpload(files);
149 + } else {
150 + const imgUrl = await handleUpload(files);
151 + // 上传失败提示
152 + if (!imgUrl.src) {
153 + files.status = "failed";
154 + files.message = "上传失败";
155 + loading.value = false;
156 + } else {
157 + files.status = "";
158 + files.message = "";
159 + fileList.value.push({
160 + // meta_id: imgUrl.meta_id,
161 + name: files.file.name,
162 + url: imgUrl.src,
163 + size: files.file.size
164 + // isImage: true,
165 + });
166 + loading.value = false;
167 + }
168 + }
169 + // 过滤非包含URL的文件
170 + fileList.value = fileList.value.filter((item) => {
171 + if (item.url) return item;
172 + });
173 + props.item.value = {
174 + key: "file_uploader",
175 + filed_name: props.item.key,
176 + // value: fileList.value.map((item) => item.url),
177 + value: fileList.value,
178 + };
179 + show_empty.value = false;
180 + // 完整数据回调到表单上
181 + emit("active", props.item.value);
182 +};
183 +
184 +// 文件删除前的回调函数
185 +const beforeDelete = (files) => {
186 + fileList.value = fileList.value.filter((item) => {
187 + if (item.url !== files.url) return item;
188 + });
189 + props.item.value = {
190 + key: "file_uploader",
191 + filed_name: props.item.key,
192 + // value: fileList.value.map((item) => item.url),
193 + value: fileList.value,
194 + };
195 + // 完整数据回调到表单上
196 + emit("active", props.item.value);
197 +};
198 +
199 +/********** 上传七牛云获取文件地址 ***********/
200 +const loading = ref(false);
201 +const formCode = $route.query.code; // 表单code
202 +
203 +// 上传文件返回文件URL
204 +const handleUpload = async (files) => {
205 + loading.value = true;
206 + // 获取HASH值
207 + // const hash = getEtag(files.content);
208 + return new Promise((resolve, reject) => {
209 + // 获取MD5值
210 + const bmf = new BMF();
211 + bmf.md5(
212 + files.file,
213 + async (err, md5) => {
214 + if (err) {
215 + console.log(err);
216 + reject(err);
217 + }
218 + // 获取七牛token
219 + const filename = files.file.name; // 真实文件名
220 + const getToken = await qiniuTokenAPI({
221 + name: filename,
222 + hash: md5,
223 + });
224 + // 文件上传七牛云
225 + let imgUrl = "";
226 + // 第一次上传
227 + if (getToken.token) {
228 + files.status = "uploading";
229 + files.message = "上传中...";
230 + // 返回数据库真实文件地址
231 + imgUrl = await uploadQiniu(files.file, getToken.token, filename, md5);
232 + }
233 + // 重复上传
234 + if (getToken.data) {
235 + imgUrl = getToken.data;
236 + }
237 + resolve(imgUrl);
238 + },
239 + (process) => {
240 + //计算进度
241 + }
242 + );
243 + });
244 +};
245 +
246 +// 多选文件上传遍历
247 +var muliUpload = async (files) => {
248 + for (let item of files) {
249 + const res = await handleUpload(item);
250 + // 上传失败提示
251 + if (!res.src) {
252 + item.status = "failed";
253 + item.message = "上传失败";
254 + loading.value = false;
255 + } else {
256 + item.status = "";
257 + item.message = "";
258 + fileList.value.push({
259 + // meta_id: res.meta_id,
260 + name: item.file.name,
261 + url: res.src,
262 + size: files.file.size
263 + // isImage: true,
264 + });
265 + loading.value = false;
266 + }
267 + }
268 +};
269 +
270 +// 生成数据库真实文件地址
271 +const uploadQiniu = async (file, token, name, md5) => {
272 + let suffix = /\.[^\.]+$/.exec(name); // 获取后缀
273 + // let affix = uuidv4();
274 + let fileName = `uploadForm/${formCode}/${md5}${suffix}`;
275 + let formData = new FormData();
276 + formData.append("file", file); // 通过append向form对象添加数据
277 + formData.append("token", token);
278 + formData.append("key", fileName);
279 + let config = {
280 + headers: { "Content-Type": "multipart/form-data" },
281 + };
282 + // 自拍文件上传七牛服务器
283 + let qiniuUploadUrl;
284 + if (window.location.protocol === 'https:') {
285 + qiniuUploadUrl = 'https://up.qbox.me';
286 + } else {
287 + qiniuUploadUrl = 'http://upload.qiniu.com';
288 + }
289 + const { filekey, hash, image_info } = await qiniuUploadAPI(
290 + qiniuUploadUrl,
291 + formData,
292 + config
293 + );
294 + if (filekey) {
295 + // 保存文件
296 + const { data } = await saveFileAPI({
297 + name,
298 + filekey,
299 + hash: md5,
300 + // format: image_info.format,
301 + // height: image_info.height,
302 + // width: image_info.width,
303 + });
304 + return data;
305 + }
306 +};
307 +
308 +/****************** END *******************/
309 +
310 +// 校验模块
311 +const validFileUploader = () => {
312 + // 必填项 未上传文件
313 + if (props.item.component_props.required && !fileList.value.length) {
314 + show_empty.value = true;
315 + } else {
316 + show_empty.value = false;
317 + }
318 + return !show_empty.value;
319 +};
320 +
321 +defineExpose({ validFileUploader });
322 +</script>
323 +
324 +<style lang="less" scoped>
325 +.file-uploader-field {
326 + .label {
327 + padding: 1rem 1rem 0 1rem;
328 + font-size: 0.9rem;
329 + font-weight: bold;
330 +
331 + span {
332 + color: red;
333 + }
334 + }
335 +
336 + .type-text {
337 + font-size: 0.9rem;
338 + margin-left: 1rem;
339 + padding-bottom: 1rem;
340 + color: gray;
341 + }
342 +}
343 +
344 +.wrapper {
345 + display: flex;
346 + align-items: center;
347 + justify-content: center;
348 + height: 100%;
349 +}
350 +
351 +.block {
352 + width: 120px;
353 + height: 120px;
354 + background-color: #fff;
355 +}
356 +</style>
1 +<!--
2 + * @Date: 2022-08-30 11:34:19
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-01 11:18:31
5 + * @FilePath: /data-table/src/components/GenderField/index.vue
6 + * @Description: 性别选择控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="gender-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <div v-if="item.component_props.note" class="note" v-html="item.component_props.note" />
15 + <van-field
16 + :name="item.name"
17 + :rules="item.rules"
18 + :required="item.component_props.required"
19 + :disabled="item.component_props.disabled"
20 + >
21 + <template #input>
22 + <van-radio-group v-model="item.value" direction="horizontal">
23 + <van-radio name="男" :checked-color="themeVars.radioColor">男</van-radio>
24 + <van-radio name="女" :checked-color="themeVars.radioColor">女</van-radio>
25 + </van-radio-group>
26 + </template>
27 + </van-field>
28 + </div>
29 +</template>
30 +
31 +<script setup>
32 +import { styleColor } from "@/constant.js";
33 +
34 +const props = defineProps({
35 + item: Object,
36 +});
37 +
38 +// TAG: 自定义主题颜色
39 +const themeVars = {
40 + radioColor: styleColor.baseColor,
41 +};
42 +// 隐藏显示
43 +const HideShow = computed(() => {
44 + return !props.item.component_props.disabled
45 +})
46 +
47 +onMounted(() => {
48 + props.item.value = props.item.component_props.default;
49 +})
50 +</script>
51 +
52 +<style lang="less" scoped>
53 +.gender-field-page {
54 + .label {
55 + padding: 1rem 1rem 0 1rem;
56 + font-size: 0.9rem;
57 + font-weight: bold;
58 + span {
59 + color: red;
60 + }
61 + }
62 + .note {
63 + font-size: 0.9rem;
64 + margin-left: 1rem;
65 + color: gray;
66 + padding-bottom: 0.5rem;
67 + white-space: pre-wrap;
68 + }
69 +}
70 +</style>
1 +<!--
2 + * @Date: 2022-09-14 14:44:30
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-01 15:07:15
5 + * @FilePath: /data-table/src/components/IdentityField/index.vue
6 + * @Description: 身份证输入控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="identity-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <!-- <div v-if="item.component_props.readonly" style="padding: 0.5rem 1rem;">{{ item.value }}</div> -->
15 + <van-field
16 + v-model="item.value"
17 + :id="item.name"
18 + :name="item.name"
19 + :placeholder="item.component_props.placeholder"
20 + :rules="rules"
21 + :required="item.component_props.required"
22 + :disabled="item.component_props.readonly"
23 + readonly
24 + @touchstart.stop="openKeyboard($event)"
25 + :border="false"
26 + >
27 + </van-field>
28 + <!-- <div v-if="gender" class="gender"><span>性别:</span>{{ gender }}</div> -->
29 + <van-number-keyboard
30 + v-model="item.value"
31 + :show="show"
32 + extra-key="X"
33 + close-button-text="完成"
34 + @blur="blurKeyboard()"
35 + @input="onInput"
36 + @delete="onDelete"
37 + safe-area-inset-bottom
38 + />
39 + </div>
40 +</template>
41 +
42 +<script setup>
43 +import $ from "jquery";
44 +import { storeToRefs, mainStore } from "@/utils/generatePackage";
45 +import { showSuccessToast, showFailToast } from "vant";
46 +
47 +const props = defineProps({
48 + item: Object,
49 +});
50 +// 隐藏显示
51 +const HideShow = computed(() => {
52 + return !props.item.component_props.disabled
53 +})
54 +const show = ref(false);
55 +let content = "";
56 +
57 +const store = mainStore();
58 +const { fieldName } = storeToRefs(store);
59 +
60 +// 监听字段变化
61 +watch(
62 + () => fieldName.value,
63 + (v) => {
64 + // 如果不是点击本输入框
65 + if (v !== props.item.name) {
66 + // 还原border颜色
67 + $(`#${props.item.name}`).parent().css("border-color", "#eaeaea");
68 + show.value = false;
69 + document.getElementById("app").style.paddingBottom = "0";
70 + }
71 + }
72 +);
73 +const readonly = props.item.component_props.readonly;
74 +const openKeyboard = (e) => {
75 + if (readonly) return false; // 如果为只读,不能设置
76 + // // 键盘上移动
77 + // const target_to_view_height = window.innerHeight - e.target.getBoundingClientRect().y; // 元素到适口高度
78 + // const target_top = document.body.scrollHeight - $(e.target).offset().top; // 元素到正文高度
79 + // let scroll_height = "";
80 + // console.warn(target_top);
81 + // if (target_top < 250) {
82 + // document.getElementById("app").style.paddingBottom = "250px";
83 + // window.scrollTo(0, $("#app").height());
84 + // } else {
85 + // // 向上滚动位置
86 + // document.documentElement.scrollTop = (target_top > 250 ? 0 : target_top) + 250;
87 + // }
88 + // 键盘上移动
89 + const target_to_view_height =
90 + window.innerHeight - e.target.getBoundingClientRect().bottom; // 元素到适口高度
91 + const target_top = document.body.scrollHeight - $(e.target).offset().top; // 元素到正文高度
92 + let scroll_height = "";
93 + if (target_to_view_height <= 250) {
94 + document.getElementById("app").style.paddingBottom = "250px";
95 + // 向上滚动位置
96 + document.documentElement.scrollTop = $(e.target).offset().top - 244;
97 + }
98 + // 选中添加border颜色
99 + content = $(e.target).parent();
100 + // TAG: 自定义主题颜色
101 + content.css("border-color", "#c2915f");
102 + setTimeout(() => {
103 + show.value = true;
104 + }, 300);
105 + // 记录点击field名
106 + store.changeFieldName(props.item.name);
107 +};
108 +const blurKeyboard = () => {
109 + show.value = false;
110 + document.getElementById("app").style.paddingBottom = "0";
111 + // 还原border颜色
112 + content.css("border-color", "#eaeaea");
113 + // 键盘失焦检查输入和添加性别显示
114 + const input_val = props.item.value;
115 + if (required && !input_val) {
116 + showFailToast("身份证号码不能为空");
117 + } else if (input_val && !/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(input_val)) {
118 + showFailToast("请输入正确身份证号码");
119 + } else {
120 + // gender.value = getGenderByIdNumber(input_val)
121 + }
122 +};
123 +
124 +// 校验函数返回 true 表示校验通过,false 表示不通过
125 +// 身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X
126 +const required = props.item.component_props.required;
127 +const validator = (val) => {
128 + if (required && !val) {
129 + return false;
130 + } else if (val && !/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(val)) {
131 + return false;
132 + } else {
133 + return true;
134 + }
135 +};
136 +// 错误提示文案
137 +const validatorMessage = (val, rule) => {
138 + if (required && !val) {
139 + return "身份证号码不能为空";
140 + } else if (val && !/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(val)) {
141 + return "请输入正确身份证号码";
142 + }
143 +};
144 +const rules = [{ validator, message: validatorMessage }];
145 +
146 +const onInput = (value) => {};
147 +const onDelete = () => {};
148 +
149 +const gender = ref('');
150 +
151 +/**
152 + * 按身份证号码获取性别
153 + * @idNumber 身份证号码
154 + * @return 男:male;女:female;异常(身份证号码为空或长度、格式错误):undefined
155 + */
156 +const getGenderByIdNumber = (idNumber) => {
157 + if (idNumber) {
158 + let genderCode; // 性别代码
159 + if (idNumber.length == 18) { // 二代身份证号码长度为18位(第17位为性别代码)
160 + genderCode = idNumber.charAt(16);
161 + } else if (idNumber.length == 15) { // 一代身份证号码长度为15位(第15位为性别代码)
162 + genderCode = idNumber.charAt(14);
163 + }
164 + if (genderCode && !isNaN(genderCode)) {
165 + // 两代身份证号码的性别代码都为男奇女偶
166 + if (parseInt(genderCode) % 2 == 0) {
167 + return '女';
168 + }
169 + return '男';
170 + }
171 + }
172 +}
173 +</script>
174 +
175 +<style lang="less" scoped>
176 +.identity-page {
177 + .label {
178 + padding: 1rem 1rem 0 1rem;
179 + font-size: 0.9rem;
180 + font-weight: bold;
181 + span {
182 + color: red;
183 + }
184 + }
185 + .gender {
186 + padding: 0 1rem 0 1rem;
187 + font-size: 0.9rem;
188 + span {
189 + font-weight: bold;
190 + }
191 + }
192 +}
193 +
194 +:deep(.van-field__body) {
195 + border: 1px solid #eaeaea;
196 + border-radius: 0.25rem;
197 + padding: 0.25rem 0.5rem;
198 +}
199 +</style>
1 +<!--
2 + * @Date: 2022-08-31 16:16:49
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-01-17 16:44:32
5 + * @FilePath: /data-table/src/components/ImageUploaderField/index.vue
6 + * @Description: 图片上传控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="image-uploader-field">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <div
15 + v-if="item.component_props.note"
16 + v-html="item.component_props.note"
17 + style="font-size: 0.9rem; margin-left: 1rem; color: gray; padding-bottom: 0.5rem; padding-top: 0.25rem; white-space: pre;"
18 + />
19 + <div style="padding: 1rem">
20 + <van-uploader
21 + :name="item.name"
22 + upload-icon="add"
23 + :before-read="beforeRead"
24 + :after-read="afterRead"
25 + :before-delete="beforeDelete"
26 + v-model="fileList"
27 + :multiple="item.component_props.max_size > 1"
28 + />
29 + </div>
30 + <div class="type-text">上传类型:&nbsp;{{ type_text }}</div>
31 + <div
32 + v-if="show_empty"
33 + class="van-field__error-message"
34 + style="padding: 0 1rem 1rem 1rem"
35 + >
36 + 图片上传不能为空
37 + </div>
38 + <van-divider />
39 + </div>
40 +
41 + <van-overlay :show="loading">
42 + <div class="wrapper" @click.stop>
43 + <van-loading vertical color="#FFFFFF">上传中...</van-loading>
44 + </div>
45 + </van-overlay>
46 +</template>
47 +
48 +<script setup>
49 +/**
50 + * 图片上传
51 + * @param name[String] 组件名称
52 + * @param image_type[Array] 图片上传类型
53 + * @param multiple[Boolean] 图片多选
54 + */
55 +import { showSuccessToast, showFailToast, showToast } from "vant";
56 +import _ from "lodash";
57 +import { v4 as uuidv4 } from "uuid";
58 +import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from "@/api/common";
59 +import BMF from "browser-md5-file";
60 +import { useRoute } from "vue-router";
61 +import axios from "axios";
62 +import { getEtag } from "@/utils/qetag.js"; // 生成hash值
63 +
64 +const $route = useRoute();
65 +const props = defineProps({
66 + item: Object,
67 +});
68 +// 隐藏显示
69 +const HideShow = computed(() => {
70 + return !props.item.component_props.disabled
71 +})
72 +const emit = defineEmits(["active"]);
73 +const show_empty = ref(false);
74 +
75 +// 固定类型限制
76 +const imageTypes = "jpg/jpeg/png/gif/bmp/psd/tif";
77 +
78 +// 文件类型中文页面显示
79 +const type_text = computed(() => {
80 + // return props.item.component_props.image_type;
81 + return imageTypes;
82 +});
83 +// 上传图片集合
84 +const fileList = ref([
85 + // { url: "https://fastly.jsdelivr.net/npm/@vant/assets/leaf.jpeg" },
86 + // Uploader 根据文件后缀来判断是否为图片文件
87 + // 如果图片 URL 中不包含类型信息,可以添加 isImage 标记来声明
88 + // { url: 'https://cloud-image', isImage: true },
89 +]);
90 +
91 +// 上传前置处理
92 +const beforeRead = (file) => {
93 + // 类型限制
94 + // const image_types = _.map(
95 + // props.item.component_props.image_type.split("/"),
96 + // (item) => `image/${item}`
97 + // );
98 + const image_types = _.map(imageTypes.split("/"), (item) => `image/${item}`);
99 +
100 + let flag = true;
101 + if (_.isArray(file)) {
102 + // 多张图片
103 + const types = _.difference(_.uniq(_.map(file, (item) => item.type)), image_types); // 数组返回不能上传的类型
104 + if (types.length) {
105 + flag = false;
106 + showFailToast("请上传指定格式图片");
107 + }
108 + if (fileList.value.length + file.length > props.item.component_props.max_count) {
109 + // 数量限制
110 + flag = false;
111 + showToast(`最大上传数量为${props.item.component_props.max_count}张`);
112 + }
113 + } else {
114 + if (!_.includes(image_types, file.type)) {
115 + showFailToast("请上传指定格式图片");
116 + flag = false;
117 + }
118 + if (fileList.value.length + 1 > props.item.component_props.max_count) {
119 + // 数量限制
120 + flag = false;
121 + showToast(`最大上传数量为${props.item.component_props.max_count}张`);
122 + }
123 + if ((file.size / 1024 / 1024).toFixed(2) > props.item.component_props.max_size) {
124 + // 体积限制
125 + flag = false;
126 + showToast(
127 + `最大文件体积为${props.item.component_props.max_size}MB`
128 + );
129 + }
130 + }
131 + return flag;
132 +};
133 +
134 +// 文件读取完成后的回调函数
135 +const afterRead = async (files) => {
136 + if (Array.isArray(files)) {
137 + // 多张图片上传files是一个数组
138 + muliUpload(files);
139 + } else {
140 + const imgUrl = await handleUpload(files);
141 + // 上传失败提示
142 + if (!imgUrl.src) {
143 + files.status = "failed";
144 + files.message = "上传失败";
145 + loading.value = false;
146 + } else {
147 + files.status = "";
148 + files.message = "";
149 + fileList.value.push({
150 + // meta_id: imgUrl.meta_id,
151 + name: files.file.name,
152 + url: imgUrl.src,
153 + // isImage: true,
154 + });
155 + loading.value = false;
156 + }
157 + }
158 + // 过滤非包含URL的图片
159 + fileList.value = fileList.value.filter((item) => {
160 + if (item.url) return item;
161 + });
162 + props.item.value = {
163 + key: "image_uploader",
164 + filed_name: props.item.key,
165 + // value: fileList.value.map((item) => item.url),
166 + value: fileList.value,
167 + };
168 + show_empty.value = false;
169 + // 完整数据回调到表单上
170 + emit("active", props.item.value);
171 +};
172 +
173 +// 文件删除前的回调函数
174 +const beforeDelete = (files) => {
175 + fileList.value = fileList.value.filter((item) => {
176 + if (item.url !== files.url) return item;
177 + });
178 + props.item.value = {
179 + key: "image_uploader",
180 + filed_name: props.item.key,
181 + // value: fileList.value.map((item) => item.url),
182 + value: fileList.value,
183 + };
184 + // 完整数据回调到表单上
185 + emit("active", props.item.value);
186 +};
187 +
188 +/********** 上传七牛云获取图片地址 ***********/
189 +const loading = ref(false);
190 +const formCode = $route.query.code; // 表单code
191 +// const uuid = () => {
192 +// let s = [];
193 +// let hexDigits = "0123456789abcdef";
194 +// for (var i = 0; i < 36; i++) {
195 +// s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
196 +// }
197 +// s[14] = "4";
198 +// s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
199 +// s[8] = s[13] = s[18] = s[23] = "-";
200 +
201 +// var uuid = s.join("");
202 +// return uuid;
203 +// };
204 +
205 +// 上传图片返回图片URL
206 +const handleUpload = async (files) => {
207 + loading.value = true;
208 + // 获取HASH值
209 + // const hash = getEtag(files.content);
210 + return new Promise((resolve, reject) => {
211 + // 获取MD5值
212 + const bmf = new BMF();
213 + bmf.md5(
214 + files.file,
215 + async (err, md5) => {
216 + if (err) {
217 + console.log(err);
218 + reject(err);
219 + }
220 + // 获取七牛token
221 + const filename = files.file.name; // 真实文件名
222 + const getToken = await qiniuTokenAPI({
223 + name: filename,
224 + hash: md5,
225 + });
226 + // 文件上传七牛云
227 + let imgUrl = "";
228 + // 第一次上传
229 + if (getToken.token) {
230 + files.status = "uploading";
231 + files.message = "上传中...";
232 + // 返回数据库真实图片地址
233 + imgUrl = await uploadQiniu(files.file, getToken.token, filename, md5);
234 + }
235 + // 重复上传
236 + if (getToken.data) {
237 + imgUrl = getToken.data;
238 + }
239 + resolve(imgUrl);
240 + },
241 + (process) => {
242 + //计算进度
243 + }
244 + );
245 + });
246 +};
247 +
248 +// 多选图片上传遍历
249 +var muliUpload = async (files) => {
250 + for (let item of files) {
251 + const res = await handleUpload(item);
252 + // 上传失败提示
253 + if (!res.src) {
254 + item.status = "failed";
255 + item.message = "上传失败";
256 + loading.value = false;
257 + } else {
258 + item.status = "";
259 + item.message = "";
260 + fileList.value.push({
261 + // meta_id: res.meta_id,
262 + name: item.file.name,
263 + url: res.src,
264 + // isImage: true,
265 + });
266 + loading.value = false;
267 + }
268 + }
269 +};
270 +
271 +
272 +
273 +const getType = (file, name) => {
274 + var index1 = name.lastIndexOf(".");
275 + var index2 = file.length;
276 + var type = file.substring(index1, index2).toUpperCase();
277 + return type;
278 +}
279 +
280 +// 生成数据库真实图片地址
281 +const uploadQiniu = async (file, token, name, md5) => {
282 + let suffix = /\.[^\.]+$/.exec(name); // 获取后缀
283 + // let affix = uuidv4();
284 + let fileName = `uploadForm/${formCode}/${md5}${suffix}`;
285 + let formData = new FormData();
286 + formData.append("file", file); // 通过append向form对象添加数据
287 + formData.append("token", token);
288 + formData.append("key", fileName);
289 + let config = {
290 + headers: { "Content-Type": "multipart/form-data" },
291 + };
292 + // 自拍图片上传七牛服务器
293 + let qiniuUploadUrl;
294 + if (window.location.protocol === 'https:') {
295 + qiniuUploadUrl = 'https://up.qbox.me';
296 + } else {
297 + qiniuUploadUrl = 'http://upload.qiniu.com';
298 + }
299 + const { filekey, hash, image_info } = await qiniuUploadAPI(
300 + qiniuUploadUrl,
301 + formData,
302 + config
303 + );
304 + if (filekey) {
305 + // 保存图片
306 + const { data } = await saveFileAPI({
307 + name,
308 + filekey,
309 + hash: md5,
310 + // format: image_info.format,
311 + height: image_info.height,
312 + width: image_info.width,
313 + });
314 + return data;
315 + }
316 +};
317 +
318 +/****************** END *******************/
319 +
320 +// 校验模块
321 +const validImageUploader = () => {
322 + // 必填项 未上传图片
323 + if (props.item.component_props.required && !fileList.value.length) {
324 + show_empty.value = true;
325 + } else {
326 + show_empty.value = false;
327 + }
328 + return !show_empty.value;
329 +};
330 +
331 +defineExpose({ validImageUploader });
332 +</script>
333 +
334 +<style lang="less" scoped>
335 +.image-uploader-field {
336 + .label {
337 + padding: 1rem 1rem 0 1rem;
338 + font-size: 0.9rem;
339 + font-weight: bold;
340 +
341 + span {
342 + color: red;
343 + }
344 + }
345 +
346 + .type-text {
347 + font-size: 0.9rem;
348 + margin-left: 1rem;
349 + padding-bottom: 1rem;
350 + color: gray;
351 + }
352 +}
353 +
354 +.wrapper {
355 + display: flex;
356 + align-items: center;
357 + justify-content: center;
358 + height: 100%;
359 +}
360 +
361 +.block {
362 + width: 120px;
363 + height: 120px;
364 + background-color: #fff;
365 +}
366 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-11-23 10:49:41
5 + * @FilePath: /data-table/src/components/MarqueeField/index.vue
6 + * @Description: 跑马灯控件
7 +-->
8 +<template>
9 + <div class="marquee-field-page">
10 + <div class="title">- {{ item.component_props.title }} -</div>
11 + <div class="scroll-wrap">
12 + <div class="scroll-content" :style="{ top }">
13 + <p v-for="x in marqueeList">
14 + <van-row>
15 + <van-col span="8">{{ x.name }}</van-col>
16 + <van-col span="8">{{ x.tel }}</van-col>
17 + <van-col span="8">{{ x.time }}</van-col>
18 + </van-row>
19 + </p>
20 + </div>
21 + </div>
22 + </div>
23 +</template>
24 +
25 +<script setup>
26 +const props = defineProps({
27 + item: Object,
28 +});
29 +
30 +onMounted(() => {
31 + getList();
32 + ScrollUp();
33 +});
34 +
35 +const top = computed(() => {
36 + return -activeIndex.value * 30 + "px";
37 +});
38 +
39 +const activeIndex = ref(0);
40 +const intNum = ref(0);
41 +const marqueeList = ref([]);
42 +// 查询名单
43 +// TODO: 数据从哪里来?
44 +const getList = () => {
45 + const arr = [];
46 + for (let index = 0; index < 100; index++) {
47 + arr.push({
48 + name: `abc${index}`,
49 + tel: "137***3456",
50 + time: `${index}分钟前`,
51 + });
52 + }
53 + console.warn(arr);
54 + marqueeList.value = arr;
55 +};
56 +//滚动播报方法
57 +const ScrollUp = () => {
58 + intNum.value = setInterval(() => {
59 + if (activeIndex.value < marqueeList.value.length) {
60 + activeIndex.value += 1;
61 + } else {
62 + activeIndex.value = 0;
63 + }
64 + }, 1000);
65 +};
66 +</script>
67 +
68 +<style lang="less" scoped>
69 +.marquee-field-page {
70 + padding-bottom: 1rem;
71 + .title {
72 + font-weight: bold;
73 + text-align: center;
74 + width: 100%;
75 + font-size: 1rem;
76 + padding: 1rem 0;
77 + }
78 + .scroll-wrap {
79 + position: relative;
80 + z-index: 2;
81 + overflow: hidden;
82 + .scroll-content {
83 + position: relative;
84 + transition: top 0.5s;
85 + height: 10rem;
86 + }
87 + .scroll-content p {
88 + /* 每行信息间隔高度 */
89 + line-height: 2;
90 + font-size: 1rem;
91 + text-align: center;
92 + }
93 + }
94 +}
95 +</style>
1 +<!--
2 + * @Date: 2022-08-30 11:34:19
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-10 14:41:52
5 + * @FilePath: /data-table/src/components/MultiRuleField/index.vue
6 + * @Description: 多选规则确认控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="multi-rule-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required" class="required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + <span v-if="item.component_props.min_count" style="color: #999; font-size: 0.85rem;">(最少选{{ item.component_props.min_count }}项)</span>
14 + </div>
15 + <van-field :name="item.key" :rules="rules" :border="false" style="padding-bottom: 0">
16 + <template #input>
17 + <van-checkbox-group v-model="item.value" style="width: 100%">
18 + <template v-for="(rule, idx) in item.component_props.options" :key="idx">
19 + <div class="multi-rule-field-box">
20 + <van-checkbox
21 + :name="rule.value"
22 + shape="square"
23 + :checked-color="themeVars.radioColor"
24 + >{{ rule.title }}</van-checkbox
25 + >
26 + <div class="van-multi-ellipsis--l3 rule-desc-text">{{ rule.desc_text }}</div>
27 + <div v-if="rule.desc_type === 'text'" class="rule-box" @click="showRule(rule)">
28 + {{ rule.desc_btn_name }}&nbsp;>>
29 + </div>
30 + <div v-if="rule.desc_type === 'url'" class="rule-box" @click="showUrl(rule)">
31 + {{ rule.desc_btn_name }}&nbsp;<van-icon name="link-o" />
32 + </div>
33 + </div>
34 + </template>
35 + </van-checkbox-group>
36 + </template>
37 + </van-field>
38 + </div>
39 +
40 + <van-overlay :show="show" :lock-scroll="false">
41 + <div class="rule-wrapper" @click.stop>
42 + <div class="rule-block">
43 + <div style="height: 70vh; min-height: 70vh; overflow: scroll; white-space: pre-wrap; line-height: 1.5;" v-html="rule_content"></div>
44 + <div class="close-btn">
45 + <van-button type="primary" block @click="closeRule"
46 + >关&nbsp;&nbsp;闭</van-button
47 + >
48 + </div>
49 + <div></div>
50 + </div>
51 + </div>
52 + </van-overlay>
53 +</template>
54 +
55 +<script setup>
56 +import { styleColor } from "@/constant.js";
57 +import $ from "jquery";
58 +
59 +const props = defineProps({
60 + item: Object,
61 +});
62 +// 隐藏显示
63 +const HideShow = computed(() => {
64 + return !props.item.component_props.disabled
65 +})
66 +// TAG: 自定义主题颜色
67 +const themeVars = {
68 + radioColor: styleColor.baseColor,
69 +};
70 +onMounted(() => {
71 + $(".rule-box").css("color", themeVars.radioColor);
72 +});
73 +const show = ref(false);
74 +const checked = ref([]);
75 +const showRule = (rule) => {
76 + show.value = true;
77 + rule.desc_text = rule.desc_text.replace(/\\n/g, "<br>")
78 + rule_content.value = rule.desc_text;
79 +};
80 +const closeRule = () => {
81 + show.value = false;
82 + rule_content.value = "";
83 +};
84 +const showUrl = (rule) => {
85 + location.href = rule.desc_url
86 +}
87 +const rule_content = ref("");
88 +const required = props.item.component_props.required;
89 +const min_count = props.item.component_props.min_count;
90 +const max_count = props.item.component_props.max_count;
91 +const validator = (val) => {
92 + if (required && !val.length) {
93 + return false;
94 + } else if (min_count && props.item.value.length < min_count) {
95 + return false;
96 + } else if (max_count && props.item.value.length > max_count) {
97 + return false;
98 + } else {
99 + return true;
100 + }
101 +};
102 +// 错误提示文案
103 +const validatorMessage = (val, rule) => {
104 + if (required && !val.length) {
105 + return "选择项不能为空";
106 + } else if (min_count && props.item.value.length < min_count) {
107 + return `最少选择${min_count}项`;
108 + } else if (max_count && props.item.value.length > max_count) {
109 + return `最多选择${max_count}项`;
110 + }
111 +};
112 +const rules = [{ validator, message: validatorMessage }];
113 +</script>
114 +
115 +<style lang="less" scoped>
116 +.multi-rule-field-page {
117 + .label {
118 + padding: 1rem 1rem 0 1rem;
119 + font-size: 0.9rem;
120 + font-weight: bold;
121 + span.required {
122 + color: red;
123 + }
124 + }
125 + .rule-desc-text {
126 + margin: 0.35rem 0.5rem 0.5rem 1.75rem;
127 + font-size: 0.8rem;
128 + color: #808080;
129 + }
130 + .rule-box {
131 + font-size: 0.85rem;
132 + margin-left: 1.8rem;
133 + padding-bottom: 0.5rem;
134 + width: fit-content;
135 + }
136 +}
137 +.rule-wrapper {
138 + display: flex;
139 + align-items: center;
140 + justify-content: center;
141 + height: 100%;
142 +}
143 +
144 +.rule-block {
145 + position: relative;
146 + width: 80vw;
147 + height: 80vh;
148 + background-color: #fff;
149 + overflow: scroll;
150 + padding: 1rem;
151 + .close-btn {
152 + position: absolute;
153 + bottom: 1rem;
154 + // transform: translateX(100%);
155 + width: calc(100% - 2rem);
156 + }
157 +}
158 +.multi-rule-field-box {
159 + border: 1px solid #eaeaea;
160 + border-radius: 0.25rem;
161 + padding: 1rem 0.5rem 0 0.5rem;
162 + // width: 100vw;
163 + margin-bottom: 0.5rem;
164 +}
165 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-01 11:21:00
5 + * @FilePath: /data-table/src/components/NameField/index.vue
6 + * @Description: 姓名输入框
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="name-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-field
15 + v-model="item.value"
16 + :name="item.name"
17 + :type="item.type"
18 + :placeholder="item.component_props.placeholder ? item.component_props.placeholder : '请输入'"
19 + :rules="item.rules"
20 + :required="item.required"
21 + :readonly="item.component_props.readonly"
22 + :disabled="item.component_props.disabled"
23 + :input-align="item.component_props.align"
24 + />
25 + </div>
26 +</template>
27 +
28 +<script setup>
29 +const props = defineProps({
30 + item: Object,
31 +});
32 +// 隐藏显示
33 +const HideShow = computed(() => {
34 + return !props.item.component_props.disabled
35 +})
36 +onMounted(() => {
37 + props.item.value = props.item.component_props.default;
38 +})
39 +</script>
40 +
41 +<style lang="less" scoped>
42 +.name-field-page {
43 + .label {
44 + padding: 1rem 1rem 0 1rem;
45 + font-size: 0.9rem;
46 + font-weight: bold;
47 + span {
48 + color: red;
49 + }
50 + }
51 +}
52 +
53 +:deep(.van-field__body) {
54 + border: 1px solid #eaeaea;
55 + border-radius: 0.25rem;
56 + padding: 0.25rem 0.5rem;
57 +}
58 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-03-08 15:28:33
5 + * @FilePath: /data-table/src/components/NoteField/index.vue
6 + * @Description: 富文本组件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="note-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required" class="required">&nbsp;*</span>
12 + <span v-if="!item.component_props.hide_label">{{ item.component_props.label }}</span>
13 + </div>
14 + <div class="html" style="padding: 0.5rem 1rem 0 1rem;" v-html="item.component_props.note"></div>
15 + </div>
16 +</template>
17 +
18 +<script setup>
19 +const props = defineProps({
20 + item: Object,
21 +});
22 +// 隐藏显示
23 +const HideShow = computed(() => {
24 + return !props.item.component_props.disabled
25 +})
26 +</script>
27 +
28 +<style lang="less">
29 +.note-field-page {
30 + .label {
31 + padding: 1rem 1rem 0 1rem;
32 + font-size: 0.9rem;
33 + font-weight: bold;
34 +
35 + span.required {
36 + color: red;
37 + }
38 + }
39 + .html {
40 + img {
41 + width: 100%;
42 + }
43 + }
44 +}
45 +</style>
1 +<!--
2 + * @Date: 2022-09-14 14:44:30
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-09 15:54:02
5 + * @FilePath: /data-table/src/components/NumberField/index.vue
6 + * @Description: 数字输入框
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="number-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <div
15 + v-if="item.component_props.note"
16 + v-html="item.component_props.note"
17 + style="font-size: 0.9rem; margin-left: 1rem; color: gray; padding-bottom: 0.5rem; padding-top: 0.25rem; white-space: pre-wrap;"
18 + />
19 + <van-field
20 + v-model="item.value"
21 + :id="item.name"
22 + :name="item.name"
23 + :placeholder="item.component_props.placeholder"
24 + :rules="rules"
25 + :required="item.component_props.required"
26 + :disabled="item.component_props.readonly"
27 + readonly
28 + @touchstart.stop="showKeyboard($event)"
29 + :border="false"
30 + >
31 + </van-field>
32 + <van-number-keyboard
33 + v-model="item.value"
34 + :show="showInteger"
35 + @blur="blurKeyboard()"
36 + @input="onInput"
37 + @delete="onDelete"
38 + safe-area-inset-bottom
39 + />
40 + <van-number-keyboard
41 + v-model="item.value"
42 + :show="showDecimal"
43 + theme="custom"
44 + extra-key="."
45 + close-button-text="完成"
46 + @blur="blurKeyboard()"
47 + @input="onInput"
48 + @delete="onDelete"
49 + safe-area-inset-bottom
50 + />
51 + </div>
52 +</template>
53 +
54 +<script setup>
55 +import $ from "jquery";
56 +import { storeToRefs, mainStore } from "@/utils/generatePackage";
57 +
58 +const props = defineProps({
59 + item: Object,
60 +});
61 +// 隐藏显示
62 +const HideShow = computed(() => {
63 + return !props.item.component_props.disabled
64 +})
65 +let content = "";
66 +
67 +const store = mainStore();
68 +const { fieldName } = storeToRefs(store);
69 +
70 +// 监听字段变化
71 +watch(
72 + () => fieldName.value,
73 + (v) => {
74 + // 如果不是点击本输入框
75 + if (v !== props.item.name) {
76 + // 还原border颜色
77 + $(`#${props.item.name}`).parent().css("border-color", "#eaeaea");
78 + if (props.item.component_props.max_fraction_count === 0) {
79 + // 显示整数键盘
80 + showInteger.value = false;
81 + } else {
82 + // 显示小数键盘
83 + showDecimal.value = false;
84 + }
85 + document.getElementById("app").style.paddingBottom = "0";
86 + }
87 + }
88 +);
89 +const readonly = props.item.component_props.readonly;
90 +const showKeyboard = (e) => {
91 + if (readonly) return false; // 如果为只读,不能设置
92 + // 键盘上移动
93 + const target_to_view_height =
94 + window.innerHeight - e.target.getBoundingClientRect().bottom; // 元素到适口高度
95 + const target_top = document.body.scrollHeight - $(e.target).offset().top; // 元素到正文高度
96 + let scroll_height = "";
97 + if (target_to_view_height <= 250) {
98 + document.getElementById("app").style.paddingBottom = "250px";
99 + // 向上滚动位置
100 + document.documentElement.scrollTop = $(e.target).offset().top - 244;
101 + }
102 + // if (target_top < 250) {
103 + // window.scrollTo(0, $("#app").height());
104 + // } else {
105 + // // 向上滚动位置
106 + // document.documentElement.scrollTop = (target_top > 500 ? 0 : target_top) + 250;
107 + // }
108 + // 选中添加border颜色
109 + content = $(e.target).parent();
110 + // TAG: 自定义主题颜色
111 + content.css("border-color", "#c2915f");
112 + setTimeout(() => {
113 + if (props.item.component_props.max_fraction_count === 0) {
114 + // 显示整数键盘
115 + showInteger.value = true;
116 + } else {
117 + // 显示小数键盘
118 + showDecimal.value = true;
119 + }
120 + }, 300);
121 + // 记录点击field名
122 + store.changeFieldName(props.item.name);
123 +};
124 +const blurKeyboard = () => {
125 + if (props.item.component_props.max_fraction_count === 0) {
126 + // 显示整数键盘
127 + showInteger.value = false;
128 + } else {
129 + // 显示小数键盘
130 + showDecimal.value = false;
131 + }
132 + document.getElementById("app").style.paddingBottom = "0";
133 + // 还原border颜色
134 + content.css("border-color", "#eaeaea");
135 +};
136 +
137 +const showDecimal = ref(false);
138 +const showInteger = ref(false);
139 +
140 +// 校验函数返回 true 表示校验通过,false 表示不通过
141 +const required = props.item.component_props.required;
142 +const min = props.item.component_props.min;
143 +const max = props.item.component_props.max;
144 +const max_count = props.item.component_props.max_fraction_count; // 保留小数个数 null=不限,0=没有小数,大于0=最多只能输入的小数个数
145 +const reg = new RegExp("^([0-9]+)(\\.(\\d){" + max_count +"," + max_count +"})$", "g");
146 +const validator = (val) => {
147 + if (required && !val) { // 必填
148 + return false;
149 + } else if (val && min && (val < min)) { // 小于最小值
150 + return false;
151 + } else if (val && max && (val > max)) { // 大于最大值
152 + return false;
153 + } else if (val && max_count && !reg.test(val)) { // 不符合保留小数个数
154 + return false;
155 + } else {
156 + return true;
157 + }
158 +};
159 +// 错误提示文案
160 +const validatorMessage = (val, rule) => {
161 + if (required && !val) {
162 + return "必填项不能为空";
163 + } else if (val && min && (val < min)) { // 小于最小值
164 + return "最小值为" + min;
165 + } else if (val && max && (val > max)) { // 大于最大值
166 + return "最大值为" + max;
167 + } else if (val && max_count && !reg.test(val)) { // 不符合保留小数个数
168 + return "保留小数点后" + max_count + "位";
169 + }
170 +};
171 +const rules = [{ validator, message: validatorMessage }];
172 +
173 +const onInput = (value) => {};
174 +const onDelete = () => {};
175 +</script>
176 +
177 +<style lang="less" scoped>
178 +.number-page {
179 + .label {
180 + padding: 1rem 1rem 0 1rem;
181 + font-size: 0.9rem;
182 + font-weight: bold;
183 + span {
184 + color: red;
185 + }
186 + }
187 +}
188 +
189 +:deep(.van-field__body) {
190 + border: 1px solid #eaeaea;
191 + border-radius: 0.25rem;
192 + padding: 0.25rem 0.5rem;
193 + input {
194 + color: #323233;
195 + }
196 +}
197 +
198 +:deep(.van-number-keyboard__title) {
199 + font-size: 1.05rem;
200 +}
201 +</style>
1 +<!--
2 + * @Date: 2022-09-02 10:46:03
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-03-20 07:45:28
5 + * @FilePath: /data-table/src/components/PhoneField/index.vue
6 + * @Description: 手机输入框
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="phone-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <!-- <van-field
15 + :id="item.name"
16 + v-model="item.value"
17 + :name="item.name"
18 + type="digit"
19 + maxlength="11"
20 + :placeholder="item.component_props.placeholder ? item.component_props.placeholder : '请输入手机号码'"
21 + :rules="rules"
22 + :required="item.component_props.required"
23 + :disabled="item.component_props.readonly"
24 + :readonly="readonly"
25 + @touchstart.stop="openKeyboard($event)"
26 + :border="false"
27 + > -->
28 + <van-field
29 + :id="item.name"
30 + v-model="item.value"
31 + :name="item.name"
32 + type="digit"
33 + maxlength="11"
34 + :placeholder="item.component_props.placeholder ? item.component_props.placeholder : '请输入手机号码'"
35 + :rules="rules"
36 + :required="item.component_props.required"
37 + :disabled="item.component_props.readonly"
38 + :border="false"
39 + >
40 + </van-field>
41 + <van-field
42 + v-if="is_sms"
43 + name="ignore"
44 + v-model="sms"
45 + center
46 + clearable
47 + label=""
48 + placeholder="请输入短信验证码"
49 + :border="false"
50 + >
51 + <template #button>
52 + <van-button size="small" type="primary">发送验证码</van-button>
53 + </template>
54 + </van-field>
55 + <van-number-keyboard
56 + v-model="item.value"
57 + :show="show"
58 + :maxlength="11"
59 + @blur="blurKeyboard()"
60 + safe-area-inset-bottom
61 + />
62 + </div>
63 +</template>
64 +
65 +<script setup>
66 +import { wxInfo } from "@/utils/tools";
67 +import $ from "jquery";
68 +import { storeToRefs, mainStore } from "@/utils/generatePackage";
69 +
70 +const props = defineProps({
71 + item: Object,
72 +});
73 +// 隐藏显示
74 +const HideShow = computed(() => {
75 + return !props.item.component_props.disabled
76 +})
77 +// web端判断
78 +const readonly = computed(() => wxInfo().isMobile);
79 +// 打开短信验证模式
80 +const is_sms = ref(false);
81 +
82 +// 校验函数返回 true 表示校验通过,false 表示不通过
83 +const validator = (val) => {
84 + if (props.item.component_props.required && !val) {
85 + return false;
86 + } else if (val && !/1\d{10}/.test(val)) {
87 + return false;
88 + } else {
89 + return true;
90 + }
91 +};
92 +// 错误提示文案
93 +const validatorMessage = (val, rule) => {
94 + if (props.item.component_props.required && !val) {
95 + return "手机号码不能为空";
96 + } else if (val && !/1\d{10}/.test(val)) {
97 + return "请输入正确手机号码";
98 + }
99 +};
100 +const rules = [{ validator, message: validatorMessage }];
101 +
102 +const show = ref(false);
103 +let content = "";
104 +
105 +const store = mainStore();
106 +const { fieldName } = storeToRefs(store);
107 +
108 +// 监听字段变化
109 +watch(
110 + () => fieldName.value,
111 + (v) => {
112 + // 如果不是点击本输入框
113 + if (v !== props.item.name) {
114 + // 还原border颜色
115 + $(`#${props.item.name}`).parent().css("border-color", "#eaeaea");
116 + show.value = false;
117 + document.getElementById("app").style.paddingBottom = "0";
118 + }
119 + }
120 +);
121 +
122 +const openKeyboard = (e) => {
123 + if (props.item.component_props.readonly) return false; // 如果为只读,不能设置
124 + // 键盘上移动
125 + const target_to_view_height =
126 + window.innerHeight - e.target.getBoundingClientRect().bottom; // 元素到适口高度
127 + const target_top = document.body.scrollHeight - $(e.target).offset().top; // 元素到正文高度
128 + let scroll_height = "";
129 + if (target_to_view_height <= 250) {
130 + document.getElementById("app").style.paddingBottom = "250px";
131 + // 向上滚动位置
132 + document.documentElement.scrollTop = $(e.target).offset().top - 244;
133 + }
134 + // 选中添加border颜色
135 + content = $(e.target).parent();
136 + // TAG: 自定义主题颜色
137 + content.css("border-color", "#c2915f");
138 + setTimeout(() => {
139 + show.value = true;
140 + }, 300);
141 + // 记录点击field名
142 + store.changeFieldName(props.item.name);
143 +};
144 +const blurKeyboard = () => {
145 + show.value = false;
146 + document.getElementById("app").style.paddingBottom = "0";
147 + // 还原border颜色
148 + content.css("border-color", "#eaeaea");
149 +};
150 +</script>
151 +
152 +<style lang="less" scoped>
153 +.phone-field-page {
154 + .label {
155 + padding: 1rem 1rem 0 1rem;
156 + font-size: 0.9rem;
157 + font-weight: bold;
158 + span {
159 + color: red;
160 + }
161 + }
162 +
163 + :deep(.van-field__body) {
164 + border: 1px solid #eaeaea;
165 + border-radius: 0.25rem;
166 + padding: 0.25rem 0.5rem;
167 + input {
168 + color: #323233;
169 + }
170 + }
171 +}
172 +</style>
1 +<!--
2 + * @Date: 2022-08-30 13:46:51
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-03-03 21:11:24
5 + * @FilePath: /data-table/src/components/PickerField/index.vue
6 + * @Description: 单列选择器组件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="picker-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-field
15 + v-model="picker_value"
16 + is-link
17 + readonly
18 + :name="item.key"
19 + :required="item.component_props.required"
20 + :placeholder="item.component_props.placeholder"
21 + :rules="item.rules"
22 + @click="showPicker = true"
23 + :border="false"
24 + />
25 + <!-- <van-field v-if="has_add_info" :name="add_info_name" v-model="add_info" label="" placeholder="请输入补充信息" :border="false" style="border: 1px solid #eaeaea;border-radius: 0.25rem; padding: 0.25rem 0.5rem; margin-top: 0.25rem;" /> -->
26 + <van-popup v-model:show="showPicker" position="bottom">
27 + <van-picker
28 + :columns="item.component_props.options"
29 + @confirm="onConfirm"
30 + @cancel="showPicker = false"
31 + />
32 + </van-popup>
33 + </div>
34 +</template>
35 +
36 +<script setup>
37 +const props = defineProps({
38 + item: Object,
39 +});
40 +const emit = defineEmits(["active"]);
41 +
42 +// 绑定值发生变化时回调,处理选项为其他时的输入项录入
43 +// const has_add_info = ref(false); // TODO: 文字不一定是其他,后续可能需要字段绑定一个值,标识是否有其他输入框进行判断
44 +// const add_info = ref('');
45 +// const add_info_name = ref(props.item.key + '#');
46 +// const add_info_key = ref('其他'); // TODO: 以后动态获取
47 +
48 +onMounted(() => {
49 + // add_info_name.value = `${props.item.key}#${add_info_key.value}`
50 + // props.item.component_props.options = props.item.component_props.options.map((opt) => {
51 + // return {
52 + // text: opt,
53 + // value: opt,
54 + // };
55 + // });
56 +});
57 +
58 +const selectedValues = ref("");
59 +const showPicker = ref(false);
60 +const picker_value = ref(props.item.component_props.default);
61 +
62 +const onConfirm = ({ selectedOptions }) => {
63 + picker_value.value = selectedOptions[0]?.value;
64 + showPicker.value = false;
65 + // 触发点自定义监听事件,配合规则显示隐藏其他字段
66 + props.item.value = { key: props.item.key, value: picker_value.value, type: "picker" };
67 + emit("active", props.item.value);
68 + // if (add_info_key.value === props.item.value) {
69 + // has_add_info.value = true;
70 + // }
71 +};
72 +// 隐藏显示
73 +const HideShow = computed(() => {
74 + return !props.item.component_props.disabled
75 +})
76 +</script>
77 +
78 +<style lang="less" scoped>
79 +.picker-field-page {
80 + margin: 1rem;
81 + .label {
82 + // padding: 1rem 1rem 0 0;
83 + font-size: 0.9rem;
84 + font-weight: bold;
85 +
86 + span {
87 + color: red;
88 + }
89 + }
90 +}
91 +
92 +:deep(.van-cell--clickable) {
93 + border: 1px solid #eaeaea;
94 + border-radius: 0.25rem;
95 + padding: 0.25rem 0.5rem;
96 + margin-top: 0.5rem;
97 +}
98 +</style>
1 +<!--
2 + * @Date: 2022-08-30 11:34:19
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-22 11:02:05
5 + * @FilePath: /data-table/src/components/RadioField/index.vue
6 + * @Description: 单项选择控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="radio-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <div v-if="item.component_props.note" class="note" v-html="item.component_props.note" />
15 + <van-field :rules="item.rules">
16 + <template #input>
17 + <van-radio-group @change="onChange(item)" v-model="radio_value" :direction="item.component_props.direction"
18 + style="width: 100%">
19 + <div v-for="x in item.component_props.options" :key="x.value" class="radio-wrapper">
20 + <van-radio :name="x.value" icon-size="1rem" :checked-color="themeVars.radioColor"
21 + style="margin-bottom: 0.25rem">{{ x.title }}</van-radio>
22 + <div v-if="x.desc_text" class="van-multi-ellipsis--l3 rule-desc-text">{{ x.desc_text }}</div>
23 + <div v-if="x.desc_type === 'text'" class="rule-box" @click="showRule(x)">
24 + {{ x.desc_btn_name }}&nbsp;>>
25 + </div>
26 + <div v-if="x.desc_type === 'url'" class="rule-box" @click="showUrl(x)">
27 + {{ x.desc_btn_name }}&nbsp;<van-icon name="link-o" />
28 + </div>
29 + <van-field v-if="radio_value === x.value && x.is_input" @blur="onBlur(x)" v-model="x.affix" label=" " label-width="5px"
30 + :placeholder="x.input_placeholder" :rules="x.input_required ? rules : ''" :required="x.input_required"
31 + class="affix-input" />
32 + </div>
33 + </van-radio-group>
34 + </template>
35 + </van-field>
36 + </div>
37 +
38 + <van-overlay :show="show" :lock-scroll="false">
39 + <div class="rule-wrapper" @click.stop>
40 + <div class="rule-block">
41 + <div style="height: 70vh; min-height: 70vh; overflow: scroll; white-space: pre-wrap; line-height: 1.5;" v-html="rule_content"></div>
42 + <div class="close-btn">
43 + <van-button type="primary" block @click="closeRule"
44 + >关&nbsp;&nbsp;闭</van-button
45 + >
46 + </div>
47 + <div></div>
48 + </div>
49 + </div>
50 + </van-overlay>
51 +</template>
52 +
53 +<script setup>
54 +import { styleColor } from "@/constant.js";
55 +import $ from "jquery";
56 +
57 +const props = defineProps({
58 + item: Object,
59 +});
60 +
61 +// TAG: 自定义主题颜色
62 +const themeVars = {
63 + radioColor: styleColor.baseColor,
64 +};
65 +// 隐藏显示
66 +const HideShow = computed(() => {
67 + return !props.item.component_props.disabled
68 +})
69 +
70 +// TODO: 等待数据结构更新,看看怎么判断必填
71 +// 校验函数返回 true 表示校验通过,false 表示不通过
72 +const validator = (val) => {
73 + if (!val) {
74 + return false;
75 + } else {
76 + return true;
77 + }
78 +};
79 +// 错误提示文案
80 +const validatorMessage = (val, rule) => {
81 + if (!val) {
82 + return "补充信息不能为空";
83 + }
84 +};
85 +const rules = [{ validator, message: validatorMessage }];
86 +
87 +const emit = defineEmits(["active"]);
88 +const radio_value = ref(props.item.component_props.default);
89 +const affix_value = ref('');
90 +
91 +const onChange = (item) => {
92 + clearAffix()
93 + // 发送自定义数据结构
94 + props.item.value = { key: props.item.key, value: radio_value.value, affix: affix_value.value, type: "radio" };
95 + emit("active", props.item.value);
96 +}
97 +const onBlur = (item) => {
98 + clearAffix()
99 + affix_value.value = item.affix ? `${item.title}: ${item.affix}` : '';
100 + // 发送自定义数据结构
101 + props.item.value = { key: props.item.key, value: radio_value.value, affix: affix_value.value, type: "radio" };
102 + emit("active", props.item.value);
103 +}
104 +const clearAffix = () => {
105 + const options = props.item.component_props.options;
106 + // 为选中项目的补充清空
107 + options.forEach(element => {
108 + if (element.value !== radio_value.value) {
109 + element.affix = ''
110 + }
111 + });
112 +}
113 +onMounted(() => {
114 + radio_value.value = props.item.component_props.default ? props.item.component_props.default : '';
115 + // 发送自定义数据结构
116 + props.item.value = { key: props.item.key, value: radio_value.value, affix: affix_value.value, type: "radio" };
117 + emit("active", props.item.value);
118 + //
119 + $(".rule-box").css("color", themeVars.radioColor);
120 +})
121 +
122 +// 补充信息弹框
123 +const show = ref(false);
124 +const showRule = (rule) => {
125 + show.value = true;
126 + rule.desc_text = rule.desc_text.replace(/\\n/g, "<br>")
127 + rule_content.value = rule.desc_text;
128 +};
129 +const closeRule = () => {
130 + show.value = false;
131 + rule_content.value = "";
132 +};
133 +const showUrl = (rule) => {
134 + location.href = rule.desc_url
135 +}
136 +const rule_content = ref("");
137 +</script>
138 +
139 +<style lang="less" scoped>
140 +.radio-field-page {
141 + .label {
142 + padding: 1rem 1rem 0 1rem;
143 + font-size: 0.9rem;
144 + font-weight: bold;
145 +
146 + span {
147 + color: red;
148 + }
149 + }
150 +
151 + .note {
152 + font-size: 0.9rem;
153 + margin-left: 1rem;
154 + color: gray;
155 + padding-bottom: 0.5rem;
156 + white-space: pre-wrap;
157 + }
158 +
159 + .radio-wrapper {
160 + border: 1px solid #eaeaea;
161 + border-radius: 0.25rem;
162 + padding: 0.25rem 0.5rem;
163 + margin-bottom: 0.25rem;
164 + }
165 + .affix-input {
166 + border: 1px solid #eaeaea;
167 + border-radius: 0.25rem;
168 + padding: 0.25rem 0.5rem;
169 + margin-top: 0.5rem;
170 + margin-bottom: 0.25rem;
171 + }
172 + .rule-desc-text {
173 + margin: 0.35rem 0.5rem 0.5rem 1.75rem;
174 + font-size: 0.8rem;
175 + color: #808080;
176 + }
177 + .rule-box {
178 + font-size: 0.85rem;
179 + margin-left: 1.8rem;
180 + padding-bottom: 0.5rem;
181 + width: fit-content;
182 + }
183 +}
184 +
185 +.rule-wrapper {
186 + display: flex;
187 + align-items: center;
188 + justify-content: center;
189 + height: 100%;
190 +}
191 +
192 +.rule-block {
193 + position: relative;
194 + width: 80vw;
195 + height: 80vh;
196 + background-color: #fff;
197 + overflow: scroll;
198 + padding: 1rem;
199 + .close-btn {
200 + position: absolute;
201 + bottom: 1rem;
202 + // transform: translateX(100%);
203 + width: calc(100% - 2rem);
204 + }
205 +}
206 +.multi-rule-field-box {
207 + border: 1px solid #eaeaea;
208 + border-radius: 0.25rem;
209 + padding: 1rem 0.5rem 0 0.5rem;
210 + // width: 100vw;
211 + margin-bottom: 0.5rem;
212 +}
213 +
214 +:deep(.van-radio) {
215 + // border: 1px solid #eaeaea;
216 + // border-radius: 0.25rem;
217 + // padding: 0.25rem 0.5rem;
218 +}
219 +</style>
1 +<!--
2 + * @Date: 2022-09-08 15:47:54
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-01-31 22:57:40
5 + * @FilePath: /data-table/src/components/RatePickerField/index.vue
6 + * @Description: 评分选择控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="rate-field">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-rate
15 + v-model="rate_value"
16 + :count="item.component_props.data_length"
17 + :readonly="item.component_props.readonly"
18 + :color="styleColor.baseColor"
19 + @change="onChange"
20 + style="padding: 1rem"
21 + />
22 + <div
23 + v-if="show_empty"
24 + class="van-field__error-message"
25 + style="padding: 0 1rem 1rem 1rem"
26 + >
27 + 评分不能为空
28 + </div>
29 + <van-divider />
30 + </div>
31 +</template>
32 +
33 +<script setup>
34 +import { styleColor } from "@/constant.js";
35 +
36 +const props = defineProps({
37 + item: Object,
38 +});
39 +// 隐藏显示
40 +const HideShow = computed(() => {
41 + return !props.item.component_props.disabled
42 +})
43 +const emit = defineEmits(["active"]);
44 +const show_empty = ref(false);
45 +const rate_value = ref(props.item.component_props.default);
46 +
47 +const onChange = (value) => {
48 + props.item.value = { key: props.item.key, value, type: "rate" };
49 + emit("active", props.item.value);
50 +};
51 +
52 +onMounted(() => {
53 + props.item.value = { key: props.item.key, value: rate_value.value, type: "rate" };
54 + emit("active", props.item.value);
55 +});
56 +
57 +const validRate = () => {
58 + // 必填项
59 + if (props.item.component_props.required && !rate_value.value) {
60 + show_empty.value = true;
61 + } else {
62 + show_empty.value = false;
63 + }
64 + return !show_empty.value;
65 +};
66 +
67 +defineExpose({ validRate });
68 +</script>
69 +
70 +<style lang="less" scoped>
71 +.rate-field {
72 + .label {
73 + padding: 1rem 1rem 0 1rem;
74 + font-size: 0.9rem;
75 + font-weight: bold;
76 +
77 + span {
78 + color: red;
79 + }
80 + }
81 +}
82 +</style>
1 +<!--
2 + * @Date: 2022-08-30 11:34:19
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-09 16:07:40
5 + * @FilePath: /data-table/src/components/RuleField/index.vue
6 + * @Description: 规则确认控件
7 +-->
8 +<template>
9 + <div class="rule-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-field
15 + :name="item.key"
16 + :rules="item.rules"
17 + :border="false"
18 + style="padding-bottom: 0"
19 + >
20 + <template #input>
21 + <van-radio-group v-model="item.value">
22 + <van-radio name="1">{{ item.component_props.rule_desc }}</van-radio>
23 + </van-radio-group>
24 + </template>
25 + </van-field>
26 + <div class="rule-box" @click="show = true">
27 + {{ item.component_props.rule_link }}&nbsp;>>
28 + </div>
29 + </div>
30 +
31 + <van-overlay :show="show" @click="show = false" :lock-scroll="false">
32 + <div class="rule-wrapper" @click.stop>
33 + <div class="rule-block">
34 + <div
35 + style="height: 70vh; overflow: scroll"
36 + v-html="item.component_props.rule_content"
37 + ></div>
38 + <div class="close-btn">
39 + <van-button type="primary" block @click="show = false"
40 + >关&nbsp;&nbsp;闭</van-button
41 + >
42 + </div>
43 + <div></div>
44 + </div>
45 + </div>
46 + </van-overlay>
47 +</template>
48 +
49 +<script setup>
50 +const props = defineProps({
51 + item: Object,
52 +});
53 +
54 +const show = ref(false);
55 +</script>
56 +
57 +<style lang="less" scoped>
58 +.rule-field-page {
59 + .label {
60 + padding: 1rem 1rem 0 1rem;
61 + font-size: 0.9rem;
62 + font-weight: bold;
63 + span {
64 + color: red;
65 + }
66 + }
67 + .rule-box {
68 + font-size: 0.85rem;
69 + margin-left: 2.7rem;
70 + padding-bottom: 1rem;
71 + color: #1989fa;
72 + }
73 +}
74 +.rule-wrapper {
75 + display: flex;
76 + align-items: center;
77 + justify-content: center;
78 + height: 100%;
79 +}
80 +
81 +.rule-block {
82 + position: relative;
83 + width: 80vw;
84 + height: 80vh;
85 + background-color: #fff;
86 + overflow: scroll;
87 + padding: 1rem;
88 + .close-btn {
89 + position: absolute;
90 + bottom: 1rem;
91 + // transform: translateX(100%);
92 + width: calc(100% - 2rem);
93 + }
94 +}
95 +</style>
1 +<!--
2 + * @Date: 2022-09-06 16:29:31
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-03-23 15:50:45
5 + * @FilePath: /data-table/src/components/SignField/index.vue
6 + * @Description: 电子签名控件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="sign-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + {{ valid }}
14 + </div>
15 + <div ref="wrapperRef" class="esign-wrapper">
16 + <!-- <div style="padding: 1rem; position: relative; height: 150px; background-color: #FCFCFC;border: 1px solid #EAEAEA; border-radius: 5px;"> -->
17 + <vue-esign v-if="esignWidth" ref="esign" class="sign-wrapper" :width="esignWidth" :height="esignHeight" :isCrop="isCrop"
18 + :lineWidth="lineWidth" :lineColor="lineColor" :bgColor.sync="bgColor" />
19 + <div v-if="show_sign" class="whiteboard">
20 + <div class="text" @click="startSign">
21 + <van-icon name="edit" />&nbsp;点击开始签署电子签名
22 + </div>
23 + </div>
24 + </div>
25 + <div v-if="!show_sign">
26 + <div v-if="show_control" class="control-sign">
27 + <van-row gutter="20" style="padding: 0 1rem">
28 + <van-col :span="12">
29 + <van-button type="default" block @click="handleGenerate">确认签名</van-button>
30 + </van-col>
31 + <van-col :span="12">
32 + <van-button type="default" block @click="cancelSign">取消签名</van-button>
33 + </van-col>
34 + </van-row>
35 + </div>
36 + <div v-else style="padding: 0 1rem">
37 + <van-button type="danger" block @click="handleReset">删除签名</van-button>
38 + </div>
39 + </div>
40 + <div v-if="show_empty" class="van-field__error-message" style="padding: 0 1rem 1rem 1rem">
41 + 电子签名不能为空
42 + </div>
43 + <van-divider />
44 + </div>
45 +
46 + <van-overlay :show="loading">
47 + <div class="wrapper" @click.stop>
48 + <van-loading vertical color="#FFFFFF">生成中...</van-loading>
49 + </div>
50 + </van-overlay>
51 +</template>
52 +
53 +<script setup>
54 +import { v4 as uuidv4 } from "uuid";
55 +import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from "@/api/common";
56 +import { showSuccessToast, showFailToast } from "vant";
57 +import { useRoute } from "vue-router";
58 +import BMF from "browser-md5-file";
59 +import { getEtag } from "@/utils/qetag.js"; // 生成hash值
60 +import dayjs from "dayjs";
61 +
62 +const props = defineProps({
63 + item: Object,
64 +});
65 +// 隐藏显示
66 +const HideShow = computed(() => {
67 + return !props.item.component_props.disabled
68 +})
69 +const $route = useRoute();
70 +const emit = defineEmits(["active"]);
71 +
72 +const esign = ref(null);
73 +
74 +let esignWidth = ref();
75 +let esignHeight = ref();
76 +const wrapperRef = ref(null)
77 +onMounted(() => {
78 + // 动态计算画板canvas宽度/高度
79 + setTimeout(() => {
80 + esignWidth.value = wrapperRef.value.offsetWidth - 32;
81 + esignHeight.value = (window.innerHeight) / 5;
82 + }, 100);
83 +})
84 +const lineWidth = ref(6);
85 +const lineColor = ref("#000000");
86 +const bgColor = ref("#FCFCFC");
87 +const isCrop = ref(false);
88 +const show_control = ref(true);
89 +const image_url = ref("");
90 +const show_empty = ref(false);
91 +
92 +const handleReset = () => {
93 + // 清空画板
94 + esign.value.reset();
95 + show_control.value = true;
96 + // 删除可能存在的签名
97 + image_url.value = "";
98 + props.item.value = {
99 + key: "sign",
100 + filed_name: props.item.key,
101 + value: "",
102 + };
103 + emit("active", props.item.value);
104 +};
105 +
106 +/********** 上传七牛云获取图片地址 ***********/
107 +const loading = ref(false);
108 +const formCode = $route.query.code; // 表单code
109 +const uuid = () => {
110 + let s = [];
111 + let hexDigits = "0123456789abcdef";
112 + for (var i = 0; i < 36; i++) {
113 + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
114 + }
115 + s[14] = "4";
116 + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
117 + s[8] = s[13] = s[18] = s[23] = "-";
118 +
119 + var uuid = s.join("");
120 + return uuid;
121 +};
122 +
123 +const uploadQiniu = async (file, token, filename) => {
124 + let formData = new FormData();
125 + formData.append("file", file); // 通过append向form对象添加数据
126 + formData.append("token", token);
127 + formData.append("key", filename);
128 + let config = {
129 + headers: { "Content-Type": "multipart/form-data" },
130 + };
131 + // 自拍图片上传七牛服务器
132 + let qiniuUploadUrl;
133 + if (window.location.protocol === 'https:') {
134 + qiniuUploadUrl = 'https://up.qbox.me';
135 + } else {
136 + qiniuUploadUrl = 'http://upload.qiniu.com';
137 + }
138 + const { filekey, hash, image_info } = await qiniuUploadAPI(
139 + qiniuUploadUrl,
140 + formData,
141 + config
142 + );
143 + if (filekey) {
144 + // 保存图片
145 + const { data } = await saveFileAPI({
146 + filekey,
147 + hash,
148 + format: image_info.format,
149 + height: image_info.height,
150 + width: image_info.width,
151 + });
152 + return data;
153 + }
154 +};
155 +/****************** END *******************/
156 +
157 +const handleUpload = async (files, filename) => {
158 + // 上传图片流程
159 + loading.value = true;
160 + // 获取HASH值
161 + const hash = getEtag(files);
162 + // 获取七牛token
163 + const { token, key, code } = await qiniuTokenAPI({
164 + name: filename,
165 + hash,
166 + });
167 + // 文件上传七牛云
168 + const imgUrl = await uploadQiniu(files, token, filename);
169 + return imgUrl;
170 +};
171 +
172 +const handleGenerate = () => {
173 + esign.value
174 + .generate()
175 + .then(async (res) => {
176 + let affix = uuidv4();
177 + let fileName = `uploadForm/${formCode}/${affix}_sign.png`;
178 + let file = dataURLtoFile(res, fileName); // 生成文件
179 + const imgUrl = await handleUpload(file, fileName);
180 + loading.value = false;
181 + props.item.value = {
182 + key: "sign",
183 + filed_name: props.item.key,
184 + value: { name: `电子签名${dayjs().format('YYYYMMDDHHmmss')}.png`, url: imgUrl.src},
185 + };
186 + image_url.value = imgUrl.src;
187 + show_control.value = false;
188 + show_empty.value = false;
189 + emit("active", props.item.value);
190 + })
191 + .catch((err) => {
192 + loading.value = false;
193 + // 签名生成失败
194 + console.warn(err);
195 + if (err) {
196 + showFailToast("签名生成失败");
197 + }
198 + });
199 +};
200 +
201 +//将图片base64转换为文件
202 +const dataURLtoFile = (dataurl, filename) => {
203 + var arr = dataurl.split(","),
204 + mime = arr[0].match(/:(.*?);/)[1],
205 + bstr = atob(arr[1]),
206 + n = bstr.length,
207 + u8arr = new Uint8Array(n);
208 + while (n--) {
209 + u8arr[n] = bstr.charCodeAt(n);
210 + }
211 + return new File([u8arr], filename, { type: mime });
212 +};
213 +
214 +const show_sign = ref(true);
215 +const startSign = () => {
216 + show_sign.value = false;
217 + show_empty.value = false;
218 +};
219 +const cancelSign = () => {
220 + show_sign.value = true;
221 + show_empty.value = false;
222 + handleReset();
223 +};
224 +
225 +const validSign = () => {
226 + // 必填项 未生成签名
227 + if (props.item.component_props.required && !image_url.value) {
228 + show_empty.value = true;
229 + } else {
230 + show_empty.value = false;
231 + }
232 + return !show_empty.value;
233 +};
234 +
235 +defineExpose({ validSign });
236 +</script>
237 +
238 +<!-- <script>
239 +export default {
240 + methods: {
241 + validSign () {
242 + console.warn(0);
243 + }
244 + }
245 +}
246 +</script> -->
247 +
248 +<style lang="less" scoped>
249 +.sign-page {
250 +
251 + // padding-bottom: 1rem;
252 + .label {
253 + padding: 1rem 1rem 0 1rem;
254 + font-size: 0.9rem;
255 + font-weight: bold;
256 +
257 + span {
258 + color: red;
259 + }
260 + }
261 +
262 + .esign-wrapper {
263 + padding: 1rem;
264 + position: relative;
265 + box-sizing: border-box;
266 +
267 + .sign-wrapper {
268 + border: 1px solid #eaeaea;
269 + border-radius: 5px;
270 + }
271 +
272 + .whiteboard {
273 + position: absolute;
274 + height: 100%;
275 + width: 100%;
276 + left: 50%;
277 + top: 50%;
278 + transform: translate(-50%, -50%);
279 + text-align: center;
280 +
281 + .text {
282 + position: absolute;
283 + width: 100%;
284 + top: 50%;
285 + left: 50%;
286 + transform: translate(-50%, -50%);
287 + }
288 + }
289 +
290 + }
291 +
292 + .control-sign {
293 + padding-bottom: 1rem;
294 + }
295 +}
296 +
297 +.wrapper {
298 + display: flex;
299 + align-items: center;
300 + justify-content: center;
301 + height: 100%;
302 +}
303 +
304 +.block {
305 + width: 120px;
306 + height: 120px;
307 + background-color: #fff;
308 +}
309 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-08-29 16:44:53
5 + * @FilePath: /data-table/src/components/TableField/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <van-field v-if="item.component_type === 'radio'" :name="item.key" :label="item.label">
10 + <template #input>
11 + <van-radio-group v-model="item.value" direction="horizontal">
12 + <van-radio v-for="x in item.sub" :key="index" :name="x.key">{{ x.value }}</van-radio>
13 + </van-radio-group>
14 + </template>
15 + </van-field>
16 + <van-field v-else v-model="item.value" :name="item.name" :label="item.label" :type="item.type"
17 + :placeholder="item.placeholder" :rules="item.rules" :required="item.required" :autosize="item.autosize"
18 + :row="item.row" />
19 +</template>
20 +
21 +<script setup>
22 +const props = defineProps({
23 + item: Object
24 +})
25 +</script>
26 +
27 +<style lang="less" scoped>
28 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-03-24 15:04:18
5 + * @FilePath: /custom_form/src/components/TextField/index.vue
6 + * @Description: 单行文本输入框(微信扫描功能)
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="text-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <div class="note-wrapper" v-if="item.component_props.note" v-html="item.component_props.note" />
15 + <nut-input v-model="item.value" :name="item.name" :type="item.type"
16 + :placeholder="item.component_props.placeholder ? item.component_props.placeholder : '请输入'" :rules="item.rules"
17 + :required="item.required"
18 + :readonly="item.component_props.readonly || (item.component_props.is_camera_scan && !item.component_props.is_edit_camera_scan_result)"
19 + :disabled="item.component_props.disabled" :input-align="item.component_props.align"
20 + :right-icon="item.component_props.is_camera_scan ? 'scan' : ''" @click-right-icon="clickRightIcon" />
21 + </div>
22 +</template>
23 +
24 +<script setup>
25 +import { getUrlParams } from "@/utils/tools";
26 +import Taro from '@tarojs/taro'
27 +
28 +const props = defineProps({
29 + item: Object,
30 +});
31 +// 隐藏显示
32 +const HideShow = computed(() => {
33 + return !props.item.component_props.disabled
34 +})
35 +
36 +// 默认识别类型
37 +const scan_type_code = ref('ALL_CODE');
38 +// 微信二维码扫描功能判断
39 +const scanType = (scan_type_code) => {
40 + switch (scan_type_code) {
41 + case 'ALL_CODE':
42 + return ["qrCode", "barCode"]
43 + case 'QR_CODE':
44 + return ["qrCode"]
45 + case 'BAR_CODE':
46 + return ["barCode"]
47 + }
48 +}
49 +
50 +// 预览模式
51 +const model = getUrlParams(location.href) ? getUrlParams(location.href).model : '';
52 +const clickRightIcon = async () => {
53 + // 预览模式屏蔽微信功能
54 + if (model === 'preview') return false;
55 + Taro.ready(() => {
56 + Taro.scanQRCode({
57 + needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
58 + scanType: scanType(props.item.camera_scan_type), // 可以指定扫二维码还是一维码,默认二者都有
59 + success: function (res) {
60 + if (res.errMsg === 'scanQRCode:ok') {
61 + let code = res.resultStr;
62 + let code_arr = code.split(",");
63 + props.item.value = code_arr[1];
64 + Taro.showToast({
65 + title: '扫描成功',
66 + icon: 'success',
67 + duration: 2000
68 + })
69 + } else {
70 + console.warn(res);
71 + Taro.showToast({
72 + title: '扫描失败',
73 + icon: 'error',
74 + duration: 2000
75 + })
76 + }
77 + },
78 + error: function (res) {
79 + if (res.errMsg.indexOf('function_not_exist') > 0) {
80 + alert('版本过低请升级')
81 + }
82 + alert(res.errMsg);
83 + },
84 + });
85 + });
86 +}
87 +</script>
88 +
89 +<style lang="less" scoped>
90 +.text-field-page {
91 + .label {
92 + padding: 1rem 1rem 0 1rem;
93 + font-size: 0.9rem;
94 + font-weight: bold;
95 +
96 + span {
97 + color: red;
98 + }
99 +
100 + .note-wrapper {
101 + font-size: 0.9rem;
102 + margin-left: 1rem;
103 + color: gray;
104 + padding-bottom: 0.5rem;
105 + padding-top: 0.25rem;
106 + white-space: pre-wrap;
107 + }
108 + }
109 +}
110 +
111 +:deep(.van-field__body) {
112 + border: 1px solid #eaeaea;
113 + border-radius: 0.25rem;
114 + padding: 0.25rem 0.5rem;
115 +}
116 +</style>
1 +<!--
2 + * @Date: 2022-08-29 14:31:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-03-22 09:13:58
5 + * @FilePath: /data-table/src/components/TextareaField/index.vue
6 + * @Description: 多行文本输入框
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="textarea-field-page">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <div
15 + v-if="item.component_props.note"
16 + v-html="item.component_props.note"
17 + style="font-size: 0.9rem; margin-left: 1rem; color: gray; padding-bottom: 0.5rem; padding-top: 0.25rem; white-space: pre-wrap;"
18 + />
19 + <van-field
20 + v-model="item.value"
21 + :name="item.name"
22 + :type="item.type"
23 + :placeholder="item.component_props.placeholder ? item.component_props.placeholder : '请输入'"
24 + :rules="item.rules"
25 + :required="item.component_props.required"
26 + :readonly="item.component_props.readonly"
27 + :rows="item.component_props.rows"
28 + autosize
29 + :maxlength="item.component_props.maxlength ? item.component_props.maxlength : null"
30 + :show-word-limit="item.component_props.maxlength"
31 + />
32 + </div>
33 +</template>
34 +
35 +<script setup>
36 +const props = defineProps({
37 + item: Object,
38 +});
39 +// 隐藏显示
40 +const HideShow = computed(() => {
41 + return !props.item.component_props.disabled
42 +})
43 +</script>
44 +
45 +<style lang="less" scoped>
46 +.textarea-field-page {
47 + .label {
48 + padding: 1rem 1rem 0 1rem;
49 + font-size: 0.9rem;
50 + font-weight: bold;
51 + span {
52 + color: red;
53 + }
54 + }
55 +}
56 +
57 +// :deep(.van-field__body) {
58 + // border: 1px solid #eaeaea;
59 + // border-radius: 0.25rem;
60 + // padding: 0.25rem 0.5rem;
61 +// }
62 +:deep(.van-cell__value) {
63 + border: 1px solid #eaeaea;
64 + border-radius: 0.25rem;
65 + padding: 0.25rem 0.5rem;
66 +}
67 +</style>
1 +<!--
2 + * @Date: 2022-08-31 11:45:30
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-02-10 10:11:05
5 + * @FilePath: /data-table/src/components/TimePickerField/index.vue
6 + * @Description: 时间选择组件
7 +-->
8 +<template>
9 + <div v-if="HideShow" class="time-picker-field">
10 + <div class="label">
11 + <span v-if="item.component_props.required">&nbsp;*</span>
12 + {{ item.component_props.label }}
13 + </div>
14 + <van-field
15 + v-model="item.value"
16 + is-link
17 + readonly
18 + :name="item.key"
19 + :required="item.component_props.required"
20 + :disabled="item.component_props.readonly"
21 + :placeholder="item.component_props.placeholder ? item.component_props.placeholder : '请选择时间'"
22 + :rules="rules"
23 + @click="onTap"
24 + :border="false"
25 + />
26 + <van-popup v-model:show="showPicker" position="bottom">
27 + <van-time-picker
28 + v-model="currentTime"
29 + title="请选择时间"
30 + :columns-type="columns_type"
31 + @confirm="onConfirm"
32 + @cancel="showPicker = false"
33 + />
34 + </van-popup>
35 + </div>
36 +</template>
37 +
38 +<script setup>
39 +import dayjs from "dayjs";
40 +
41 +const props = defineProps({
42 + item: Object,
43 +});
44 +// 隐藏显示
45 +const HideShow = computed(() => {
46 + return !props.item.component_props.disabled
47 +})
48 +const showPicker = ref(false);
49 +const currentTime = ref([]);
50 +const readonly = props.item.component_props.readonly;
51 +
52 +const onTap = () => {
53 + if (readonly) return false; // 如果为只读,不能设置
54 + showPicker.value = true
55 +}
56 +
57 +const onConfirm = ({ selectedValues, selectedOptions }) => {
58 + props.item.value = selectedValues.join(":");
59 + showPicker.value = false;
60 +};
61 +
62 +const columns_type = ref([]);
63 +const date_format = props.item.component_props.data_dateformat; // HH:mm=时分,HH:mm:ss=时分秒
64 +onMounted(() => {
65 + // 根据默认值时间调整显示
66 + currentTime.value = props.item.component_props.default ? props.item.component_props.default.split(":") : props.item.value.split(":");
67 + let Hour = ''
68 + let Minute = ''
69 + let Second = ''
70 + if (!props.item.component_props.default) {
71 + Hour = String(dayjs().hour());
72 + Minute = String(dayjs().minute());
73 + Second = String(dayjs().second());
74 + } else {
75 + Hour = currentTime.value[0];
76 + Minute = currentTime.value[1];
77 + Second = currentTime.value[2];
78 + }
79 + switch (date_format) {
80 + case "HH:mm":
81 + columns_type.value = ['hour', 'minute'];
82 + // 设置默认值
83 + currentTime.value = [Hour, Minute];
84 + break;
85 + case "HH:mm:ss":
86 + columns_type.value = ['hour', 'minute', 'second'];
87 + // 设置默认值
88 + currentTime.value = [Hour, Minute, Second];
89 + break;
90 + }
91 +});
92 +
93 +const required = props.item.component_props.required;
94 +const data_minvalue = props.item.component_props.data_minvalue;
95 +const data_maxvalue = props.item.component_props.data_maxvalue;
96 +const validator = (val) => {
97 + if (required && !val) {
98 + return false;
99 + } else if (val && data_minvalue && val < data_minvalue) {
100 + return false;
101 + } else if (val && data_maxvalue && val > data_maxvalue) {
102 + return false;
103 + } else {
104 + return true;
105 + }
106 +};
107 +// 错误提示文案
108 +const validatorMessage = (val, rule) => {
109 + if (required && !val) {
110 + return "必填项不能为空";
111 + } else if (val && data_minvalue && val < data_minvalue) {
112 + return "最小可选:" + data_minvalue;
113 + } else if (val && data_maxvalue && val > data_maxvalue) {
114 + return "最大可选:" + data_maxvalue;
115 + }
116 +};
117 +const rules = [{ validator, message: validatorMessage }];
118 +</script>
119 +
120 +<style lang="less" scoped>
121 +.time-picker-field {
122 + margin: 1rem;
123 + .label {
124 + // padding: 1rem 1rem 0 1rem;
125 + font-size: 0.9rem;
126 + font-weight: bold;
127 +
128 + span {
129 + color: red;
130 + }
131 + }
132 +
133 + :deep(.van-icon) { // 处理正式服务器上箭头上下位移问题
134 + font-size: var(--van-cell-icon-size);
135 + line-height: var(--van-cell-line-height);
136 + }
137 +}
138 +
139 +:deep(.van-cell--clickable) {
140 + border: 1px solid #eaeaea;
141 + border-radius: 0.25rem;
142 + padding: 0.25rem 0.5rem;
143 + margin-top: 0.5rem;
144 + input {
145 + color: #323233;
146 + }
147 +}
148 +</style>
1 +<!--
2 + * @Author: hookehuyr hookehuyr@gmail.com
3 + * @Date: 2022-05-23 13:42:35
4 + * @LastEditors: hookehuyr hookehuyr@gmail.com
5 + * @LastEditTime: 2022-11-22 17:12:52
6 + * @FilePath: /data-table/src/components/VideoField/index.vue
7 + * @Description:
8 +-->
9 +<template>
10 + <div class="video-wrapper">
11 + <div class="label">
12 + {{ item.component_props.label }}
13 + </div>
14 + <div id="mui-player" class="video-div" />
15 + </div>
16 +</template>
17 +
18 +<script setup>
19 +/**
20 + * 视频组件通用模块
21 + */
22 +import "mui-player/dist/mui-player.min.css";
23 +import MuiPlayer from "mui-player";
24 +import { onMounted } from "vue";
25 +import { useEventListener } from "@/composables";
26 +
27 +// 视频基础属性
28 +const props = defineProps({
29 + item: Object,
30 +});
31 +// 视频播放事件回调
32 +const emit = defineEmits(["active"]);
33 +let video = null;
34 +onMounted(() => {
35 + const mp = new MuiPlayer({
36 + container: "#mui-player",
37 + // title: props.item.component_props.title,
38 + src: props.item.component_props.src,
39 + poster: props.item.component_props.cover,
40 + autoFit: false,
41 + videoAttribute: [
42 + // 声明启用同层播放, 不让会自动全屏播放
43 + { attrKey: "webkit-playsinline", attrValue: "webkit-playsinline" },
44 + { attrKey: "playsinline", attrValue: "playsinline" },
45 + { attrKey: "x5-video-player-type", attrValue: "h5-page" },
46 + ],
47 + });
48 + video = mp.video();
49 + //视频播放事件监听
50 + video.addEventListener("play", function () {
51 + props.item.value = { key: "video", value: "play" };
52 + emit("active", props.item.value);
53 + });
54 + // 配置16:9高度比
55 + const width = document.getElementById("mui-player").clientWidth;
56 + const height = (width * 9) / 16;
57 + document.getElementById("mui-player").height = height;
58 +});
59 +
60 +onUnmounted(() => {
61 + video.removeEventListener("play", function () {});
62 +});
63 +</script>
64 +
65 +<style lang="less" scoped>
66 +.video-wrapper {
67 + position: relative;
68 + margin: 1rem 0;
69 + border-bottom-left-radius: 5px;
70 + border-bottom-right-radius: 5px;
71 + box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.13);
72 +
73 + .video-div {
74 + // border-top-left-radius: 5px;
75 + // border-top-right-radius: 5px;
76 + border-radius: 5px;
77 + }
78 +
79 + .video-bar {
80 + color: #713610;
81 + padding: 1rem;
82 + padding-bottom: 0.5rem;
83 + }
84 +}
85 +
86 +:deep(.back-button) {
87 + display: none !important;
88 +}
89 +</style>
1 +export default [
2 + {
3 + "id": 315040,
4 + "localism_type": "普通话",
5 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653095780.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:3kZFL-LK1CP0k8Iiewr6d4_kCas=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTU3ODAucG5nKiIsIkUiOjE5Njg2NjUzMTB9",
6 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653095775.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:2gmHTr1opoFCeuYjRPvxabflLZM=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTU3NzUubXA0KiIsIkUiOjE5Njg2NjUzMTB9",
7 + "note": "Z",
8 + "status": "apply",
9 + "check_note": null,
10 + "kg_id": 314048,
11 + "kg_name": "上海市杨浦区科技幼儿园",
12 + "perf_id": 314880,
13 + "name": "王申羽",
14 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
15 + "book_id": 314483,
16 + "book_name": "《它们从哪里来》",
17 + "comment_num": 0,
18 + "like_num": 0,
19 + "favor_num": 0,
20 + "play_num": 0,
21 + "is_like": 0
22 + },
23 + {
24 + "id": 315041,
25 + "localism_type": "沪语",
26 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653095788.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:km1-cl6r3X9BwYekqZHyo4kzbWo=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTU3ODgucG5nKiIsIkUiOjE5Njg2NjUzMTB9",
27 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653095786.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:9kK2Nf96oLV0_t5dKXLnEC0QBLM=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTU3ODYubXA0KiIsIkUiOjE5Njg2NjUzMTB9",
28 + "note": "Z",
29 + "status": "apply",
30 + "check_note": null,
31 + "kg_id": 314048,
32 + "kg_name": "上海市杨浦区科技幼儿园",
33 + "perf_id": 314880,
34 + "name": "王申羽",
35 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
36 + "book_id": 314483,
37 + "book_name": "《它们从哪里来》",
38 + "comment_num": 0,
39 + "like_num": 0,
40 + "favor_num": 0,
41 + "play_num": 0,
42 + "is_like": 0
43 + },
44 + {
45 + "id": 315036,
46 + "localism_type": "普通话",
47 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653095579.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:TbYtglmAQrmsCpKulfIkdjrYsjc=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTU1NzkucG5nKiIsIkUiOjE5Njg2NjUxMjd9",
48 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653095576.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:CIcNhT-g-lmC1mv253Nnoz7EVKQ=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTU1NzYubXA0KiIsIkUiOjE5Njg2NjUxMjd9",
49 + "note": "Z",
50 + "status": "apply",
51 + "check_note": null,
52 + "kg_id": 314048,
53 + "kg_name": "上海市杨浦区科技幼儿园",
54 + "perf_id": 314880,
55 + "name": "王申羽",
56 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
57 + "book_id": 314483,
58 + "book_name": "《它们从哪里来》",
59 + "comment_num": 0,
60 + "like_num": 0,
61 + "favor_num": 0,
62 + "play_num": 0,
63 + "is_like": 0
64 + },
65 + {
66 + "id": 315037,
67 + "localism_type": "沪语",
68 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653095586.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:MaeVQ6AUQNzcWKDaMExdr7pFpZU=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTU1ODYucG5nKiIsIkUiOjE5Njg2NjUxMjd9",
69 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653095584.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:Ox6WCAeG4ImThDZaoEc2v3mrcb8=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTU1ODQubXA0KiIsIkUiOjE5Njg2NjUxMjd9",
70 + "note": "Z",
71 + "status": "apply",
72 + "check_note": null,
73 + "kg_id": 314048,
74 + "kg_name": "上海市杨浦区科技幼儿园",
75 + "perf_id": 314880,
76 + "name": "王申羽",
77 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
78 + "book_id": 314483,
79 + "book_name": "《它们从哪里来》",
80 + "comment_num": 0,
81 + "like_num": 0,
82 + "favor_num": 0,
83 + "play_num": 0,
84 + "is_like": 0
85 + },
86 + {
87 + "id": 315033,
88 + "localism_type": "沪语",
89 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653095145.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:dzCQntH7aBmPGYyxvmiOTwgr8WY=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTUxNDUucG5nKiIsIkUiOjE5Njg2NjQ2Njh9",
90 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653095142.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:IygI0aLGuFR_DEgRtmsbNjAhVS0=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTUxNDIubXA0KiIsIkUiOjE5Njg2NjQ2Njh9",
91 + "note": "2",
92 + "status": "apply",
93 + "check_note": null,
94 + "kg_id": 314048,
95 + "kg_name": "上海市杨浦区科技幼儿园",
96 + "perf_id": 314880,
97 + "name": "王申羽",
98 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
99 + "book_id": 314483,
100 + "book_name": "《它们从哪里来》",
101 + "comment_num": 0,
102 + "like_num": 0,
103 + "favor_num": 0,
104 + "play_num": 0,
105 + "is_like": 0
106 + },
107 + {
108 + "id": 315032,
109 + "localism_type": "普通话",
110 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653095136.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:PLQaeVUgpZ-rgvD37DE0Yq9W5-o=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTUxMzYucG5nKiIsIkUiOjE5Njg2NjQ2Njh9",
111 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653095134.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:1fnWx_k8UBYJJ_XjunwCp93N7GI=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTUxMzQubXA0KiIsIkUiOjE5Njg2NjQ2Njh9",
112 + "note": "1",
113 + "status": "apply",
114 + "check_note": null,
115 + "kg_id": 314048,
116 + "kg_name": "上海市杨浦区科技幼儿园",
117 + "perf_id": 314880,
118 + "name": "王申羽",
119 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
120 + "book_id": 314483,
121 + "book_name": "《它们从哪里来》",
122 + "comment_num": 0,
123 + "like_num": 0,
124 + "favor_num": 0,
125 + "play_num": 0,
126 + "is_like": 0
127 + },
128 + {
129 + "id": 315024,
130 + "localism_type": "普通话",
131 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653094501.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:0fDZL_C8youKy-IftSFwyr3c36g=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTQ1MDEucG5nKiIsIkUiOjE5Njg2NjQwNDF9",
132 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653094499.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:BHD2B4D_CDzu9Tw48SvPAgfuJ90=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTQ0OTkubXA0KiIsIkUiOjE5Njg2NjQwNDF9",
133 + "note": "1",
134 + "status": "apply",
135 + "check_note": null,
136 + "kg_id": 314048,
137 + "kg_name": "上海市杨浦区科技幼儿园",
138 + "perf_id": 314880,
139 + "name": "王申羽",
140 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
141 + "book_id": 314483,
142 + "book_name": "《它们从哪里来》",
143 + "comment_num": 0,
144 + "like_num": 0,
145 + "favor_num": 0,
146 + "play_num": 0,
147 + "is_like": 0
148 + },
149 + {
150 + "id": 315025,
151 + "localism_type": "沪语",
152 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653094509.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:qSjAFNlU3o6SduJzStxg49A8SK0=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTQ1MDkucG5nKiIsIkUiOjE5Njg2NjQwNDF9",
153 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653094506.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:2Khn4cIo2mJo6Ty6qRvLB0Uus48=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTQ1MDYubXA0KiIsIkUiOjE5Njg2NjQwNDF9",
154 + "note": "2",
155 + "status": "apply",
156 + "check_note": null,
157 + "kg_id": 314048,
158 + "kg_name": "上海市杨浦区科技幼儿园",
159 + "perf_id": 314880,
160 + "name": "王申羽",
161 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
162 + "book_id": 314483,
163 + "book_name": "《它们从哪里来》",
164 + "comment_num": 0,
165 + "like_num": 0,
166 + "favor_num": 0,
167 + "play_num": 0,
168 + "is_like": 0
169 + },
170 + {
171 + "id": 315021,
172 + "localism_type": "沪语",
173 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653094433.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:AfRlQR8HGWV0y9kAeEAKd7jhj6I=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTQ0MzMucG5nKiIsIkUiOjE5Njg2NjM5NTV9",
174 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653094431.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:PjpJx-lC2kILJPBlpPJuJMqslrE=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTQ0MzEubXA0KiIsIkUiOjE5Njg2NjM5NTV9",
175 + "note": "Z",
176 + "status": "apply",
177 + "check_note": null,
178 + "kg_id": 314048,
179 + "kg_name": "上海市杨浦区科技幼儿园",
180 + "perf_id": 314880,
181 + "name": "王申羽",
182 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
183 + "book_id": 314483,
184 + "book_name": "《它们从哪里来》",
185 + "comment_num": 0,
186 + "like_num": 0,
187 + "favor_num": 0,
188 + "play_num": 0,
189 + "is_like": 0
190 + },
191 + {
192 + "id": 315020,
193 + "localism_type": "普通话",
194 + "cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653094425.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:9BOVLskMMLE-pbqmb5rK_5qncNQ=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTQ0MjUucG5nKiIsIkUiOjE5Njg2NjM5NTV9",
195 + "video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653094423.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:uEz_Ke-BuliSvPQkvdwOAe42Hyk=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTQ0MjMubXA0KiIsIkUiOjE5Njg2NjM5NTV9",
196 + "note": "Z",
197 + "status": "apply",
198 + "check_note": null,
199 + "kg_id": 314048,
200 + "kg_name": "上海市杨浦区科技幼儿园",
201 + "perf_id": 314880,
202 + "name": "王申羽",
203 + "avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
204 + "book_id": 314483,
205 + "book_name": "《它们从哪里来》",
206 + "comment_num": 0,
207 + "like_num": 0,
208 + "favor_num": 0,
209 + "play_num": 0,
210 + "is_like": 0
211 + }
212 +]
1 +<!--
2 + * @Author: hookehuyr hookehuyr@gmail.com
3 + * @Date: 2022-05-23 18:00:39
4 + * @LastEditors: hookehuyr hookehuyr@gmail.com
5 + * @LastEditTime: 2022-06-02 17:41:38
6 + * @FilePath: /tswj/src/components/MuiVideo/test.vue
7 + * @Description: 视频播放通用组件演示组件
8 + * @Description: type: video 为纯视频播放框,bookDetail为定制模式
9 +-->
10 +<template>
11 + <mui-video
12 + v-for="(item, index) in mock"
13 + :key="index"
14 + :item="item"
15 + type="video"
16 + @on-play="onPlay"
17 + />
18 +</template>
19 +
20 +<script setup>
21 + import mock from '@/components/MuiVideo/mock';
22 + import MuiVideo from '@/components/MuiVideo/index';
23 + import { ref } from 'vue';
24 + import { useRoute, useRouter } from 'vue-router';
25 +
26 + import {
27 + Cookies,
28 + $,
29 + _,
30 + axios,
31 + storeToRefs,
32 + mainStore,
33 + Toast,
34 + useTitle,
35 + } from '@/utils/generatePackage.js';
36 + //import { } from '@/utils/generateModules.js'
37 + //import { } from '@/utils/generateIcons.js'
38 + //import { } from '@/composables'
39 + const $route = useRoute();
40 + const $router = useRouter();
41 + useTitle($route.meta.title);
42 +
43 + const onPlay = ({ event, props }) => {
44 + console.warn(event);
45 + console.warn(props);
46 + };
47 +</script>
48 +
49 +<style
50 + lang="less"
51 + scoped></style>
1 +/**
2 + * 依赖注入命名集合
3 + */
4 +export const myInjectionKey = Symbol()
5 +export const fooInjectionKey = Symbol()
1 +import _ from '@/utils/lodash'
2 +import TextField from '@/components/TextField/index.vue'
3 +// import TextareaField from '@/components/TextareaField/index.vue'
4 +// import RadioField from '@/components/RadioField/index.vue'
5 +// import CheckboxField from '@/components/CheckboxField/index.vue'
6 +// import PickerField from '@/components/PickerField/index.vue'
7 +// import AreaPickerField from '@/components/AreaPickerField/index.vue'
8 +// import DatePickerField from '@/components/DatePickerField/index.vue'
9 +// import TimePickerField from '@/components/TimePickerField/index.vue'
10 +// import DateTimePickerField from '@/components/DateTimePickerField/index.vue'
11 +// import ImageUploaderField from '@/components/ImageUploaderField/index.vue'
12 +// import FileUploaderField from '@/components/FileUploaderField/index.vue'
13 +// import PhoneField from '@/components/PhoneField/index.vue'
14 +// import EmailField from '@/components/EmailField/index.vue'
15 +// import SignField from '@/components/SignField/index.vue'
16 +// import RatePickerField from '@/components/RatePickerField/index.vue'
17 +// import CalendarField from '@/components/CalendarField/index.vue'
18 +// import IdentityField from '@/components/IdentityField/index.vue'
19 +// import NumberField from '@/components/NumberField/index.vue'
20 +// import DesField from '@/components/DesField/index.vue'
21 +// import DividerField from '@/components/DividerField/index.vue'
22 +// import VideoField from '@/components/VideoField/index.vue'
23 +// import MarqueeField from '@/components/MarqueeField/index.vue'
24 +// import ContactField from '@/components/ContactField/index.vue'
25 +// import RuleField from '@/components/RuleField/index.vue'
26 +// import MultiRuleField from '@/components/MultiRuleField/index.vue'
27 +// import ButtonField from '@/components/ButtonField/index.vue'
28 +// import NoteField from '@/components/NoteField/index.vue'
29 +// import NameField from '@/components/NameField/index.vue'
30 +// import GenderField from '@/components/GenderField/index.vue'
31 +
32 +/**
33 + * 生成自定义组件类型
34 + * @param {*} data
35 + * @type input 单行文本 TextField
36 + * @type textarea 多行文本 TextareaField
37 + * @type radio 单项选择 RadioField
38 + * @type checkbox 多项选择 CheckboxField
39 + * @type select 单列选择器 PickerField
40 + * @type area_picker 地址选择器 AreaPickerField
41 + * @type date_picker 日期选择器 DatePickerField
42 + * @type time_picker 时间选择器 TimePickerField
43 + * @type datetime_picker 日期时间选择器 DateTimePickerField
44 + * @type image_uploader 图片上传 ImageUploaderField
45 + * @type phone 手机输入框 PhoneField
46 + * @type email 邮箱输入框 EmailField
47 + * @type sign 电子签名输入框 SignField
48 + * @type rate_picker 评分选择器 RatePickerField
49 + * @type calendar 日历选择器 CalendarField
50 + * @type id_code 身份证输入框 IdentityField
51 + * @type desc 文字描述 DesField
52 + * @type divider 分割线 DividerField
53 + * @type video 视频控件 VideoField
54 + * @type marquee 跑马灯控件 MarqueeField
55 + * @type rule 活动规则控件 RuleField
56 + * @type multi_rule 活动规则控件 MultiRuleField
57 + * @type note 富文本控件 NoteField
58 + * @type name 姓名控件 NameField
59 + * @type gender 性别控件 GenderField
60 + */
61 +export function createComponentType(data) {
62 + // 判断类型和使用组件
63 + _.each(data, (item, index) => {
64 + // 必填项规则添加
65 + if (item.component_props.required) {
66 + item.rules = [
67 + {
68 + required: true,
69 + message: item.placeholder ? item.placeholder : '必填项不能为空',
70 + },
71 + ]
72 + }
73 + if (item.component_props.tag === 'input') {
74 + item.type = 'text'
75 + item.name = item.key
76 + item.component = TextField
77 + }
78 + // if (item.component_props.tag === 'textarea') {
79 + // item.type = 'textarea'
80 + // item.name = item.key
81 + // // item.rows = 10;
82 + // item.autosize = true
83 + // item.component = TextareaField
84 + // }
85 + // if (item.component_props.tag === 'number') {
86 + // item.name = item.key
87 + // item.component = NumberField
88 + // }
89 + // if (item.component_props.tag === 'radio') {
90 + // item.component = RadioField
91 + // }
92 + // if (item.component_props.tag === 'checkbox') {
93 + // item.component = CheckboxField
94 + // }
95 + // if (item.component_props.tag === 'select') {
96 + // item.component = PickerField
97 + // }
98 + // if (item.component_props.tag === 'address') {
99 + // item.component = AreaPickerField
100 + // }
101 + // if (item.component_props.tag === 'date') {
102 + // item.component = DatePickerField
103 + // }
104 + // if (item.component_props.tag === 'time') {
105 + // item.component = TimePickerField
106 + // }
107 + // if (item.component_props.tag === 'datetime') {
108 + // item.component = DateTimePickerField
109 + // }
110 + // if (item.component_props.tag === 'image_uploader') {
111 + // item.component = ImageUploaderField
112 + // }
113 + // if (item.component_props.tag === 'file_uploader') {
114 + // item.component = FileUploaderField
115 + // }
116 + // if (item.component_props.tag === 'phone') {
117 + // item.name = item.key
118 + // item.component = PhoneField
119 + // }
120 + // if (item.component_props.tag === 'email') {
121 + // item.name = item.key
122 + // item.component = EmailField
123 + // }
124 + // if (item.component_props.tag === 'sign') {
125 + // item.name = item.key
126 + // item.component = SignField
127 + // }
128 + // if (item.component_props.tag === 'rate') {
129 + // item.name = item.key
130 + // item.component = RatePickerField
131 + // }
132 + // if (item.component_props.tag === 'calendar') {
133 + // item.name = item.key
134 + // item.component = CalendarField
135 + // }
136 + // if (item.component_props.tag === 'id_card') {
137 + // item.name = item.key
138 + // item.component = IdentityField
139 + // }
140 + // if (item.component_props.tag === 'desc') {
141 + // item.name = item.key
142 + // item.component = DesField
143 + // }
144 + // if (item.component_props.tag === 'divider') {
145 + // item.name = item.key
146 + // item.component = DividerField
147 + // }
148 + // if (item.component_props.tag === 'video') {
149 + // item.name = item.key
150 + // item.component = VideoField
151 + // }
152 + // if (item.component_props.tag === 'marquee') {
153 + // item.name = item.key
154 + // item.component = MarqueeField
155 + // }
156 + // if (item.component_props.tag === 'contact') {
157 + // item.name = item.key
158 + // item.component = ContactField
159 + // }
160 + // if (item.component_props.tag === 'rule') {
161 + // item.name = item.key
162 + // item.component = RuleField
163 + // }
164 + // if (item.component_props.tag === 'button') {
165 + // item.name = item.key
166 + // item.component = ButtonField
167 + // }
168 + // if (item.component_props.tag === 'multi_rule') {
169 + // item.name = item.key
170 + // item.value = []
171 + // item.component = MultiRuleField
172 + // }
173 + // if (item.component_props.tag === 'note') {
174 + // item.name = item.key
175 + // item.component = NoteField
176 + // }
177 + // if (item.component_props.tag === 'name') {
178 + // item.name = item.key
179 + // item.component = NameField
180 + // }
181 + // if (item.component_props.tag === 'gender') {
182 + // item.name = item.key
183 + // item.component = GenderField
184 + // }
185 + })
186 +}
1 +import { provide, inject } from "vue";
2 +
3 +// const key = Symbol();
4 +
5 +/**
6 + * 创建全局变量
7 + * @param {*} context
8 + * @param {*} key
9 + */
10 +export function createContext(context, key) {
11 + provide(key, context)
12 +}
13 +/**
14 + * 使用全局变量
15 + * @param {*} key
16 + * @returns
17 + */
18 +export function useContext(key) {
19 + return inject(key)
20 +}
1 +/*
2 + * @Author: hookehuyr hookehuyr@gmail.com
3 + * @Date: 2022-05-28 22:31:25
4 + * @LastEditors: hookehuyr hookehuyr@gmail.com
5 + * @LastEditTime: 2022-05-30 10:18:09
6 + * @FilePath: /tswj/src/hooks/useDebounce.js
7 + * @Description:
8 + */
9 +import _ from 'lodash';
10 +/**
11 + * 封装lodash防抖
12 + * @param {*} fn 执行函数
13 + * @param {*} timestamp 执行间隔
14 + * @param {*} options 函数配置 - 在延迟开始前调用,在延迟结束后不调用
15 + * @returns 返回新的 debounced(防抖动)函数。
16 + */
17 +export const useDebounce = (fn, timestamp = 500, options = { leading: true, trailing: false }) => {
18 + return _.debounce(fn, timestamp, options);
19 +}
1 +
2 +/**
3 + * @description 封装简化滚动查询列表执行流程
4 + * @param {*} data 接口返回列表数据
5 + * @param {*} list 自定义列表
6 + * @param {*} offset
7 + * @param {*} loading
8 + * @param {*} finished
9 + * @param {*} finishedTextStatus
10 + * @param {*} emptyStatus
11 + */
12 +import _ from 'lodash'
13 +
14 +export const flowFn = (data, list, offset, loading, finished, finishedTextStatus, emptyStatus) => {
15 + list.value = _.concat(list.value, data);
16 + list.value = _.uniqBy(list.value, 'id');
17 + offset.value = list.value.length;
18 + loading.value = false;
19 + // 数据全部加载完成
20 + if (!data.length) {
21 + // 加载状态结束
22 + finished.value = true;
23 + }
24 + // 空数据提示
25 + if (!list.value.length) {
26 + finishedTextStatus.value = false;
27 + }
28 + emptyStatus.value = Object.is(list.value.length, 0);
29 +}
1 +/*
2 + * @Date: 2022-07-21 13:28:05
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-08-22 09:51:52
5 + * @FilePath: /front/src/hooks/useGo.js
6 + * @Description: 文件描述
7 + */
8 +import { useRouter } from 'vue-router';
9 +import { getArticleAPI } from '@/api'
10 +import { Cookies } from '@/utils/generatePackage'
11 +import { parseQueryString } from '@/utils/tools'
12 +
13 +/**
14 + * 封装路由跳转方便行内调用
15 + * @returns
16 + */
17 +export function useGo () {
18 + let router = useRouter()
19 + function go (path, query) {
20 + router.push({
21 + path: path,
22 + query: query
23 + })
24 + }
25 + return go
26 +}
27 +
28 +/**
29 + * 封装跳转文章详情页
30 + * @param id 文章ID
31 + * @param cid 栏目ID
32 + * @param column 栏目名称
33 + * @param section 二级栏目名称
34 + * @param name 子栏目名称
35 + * @returns
36 + */
37 +export function useGoTo () {
38 + let router = useRouter()
39 + // function detail({ id, cid, column, section, name, post_link }) {
40 + function detail(item) {
41 + // 保存点击位置
42 + // Cookies.set('scrollTop', getScrollTop());
43 + // Cookies.set('scrollTopId', id);
44 + // 判断是否跳转URL
45 + // const { code, data } = await getArticleAPI({ f: 'article', i: id, cid });
46 + // if (code) {
47 + // if (data.post_link) { // 优先显示链接文章
48 + // location.href = data.post_link;
49 + // return false;
50 + // }
51 + // router.push({
52 + // path: '/detail',
53 + // query: { cid, id, column, section, name }
54 + // });
55 + // }
56 + if (item) {
57 + if (item.post_link) {
58 + if (item.post_link.indexOf('f/guanzong/web')) {
59 + item.post_link = item.post_link.replace('web', 'front');
60 + }
61 + if (item.post_link.indexOf('f/guanzong/web/#/list') > 0 || item.post_link.indexOf('f/guanzong/front/#/list') > 0) {
62 + router.push({
63 + path: '/list',
64 + query: parseQueryString(item.post_link)
65 + });
66 + return false;
67 + } else if (item.post_link) { // 优先显示链接文章
68 + location.href = item.post_link;
69 + return false;
70 + }
71 + } else {
72 + router.push({
73 + path: '/detail',
74 + query: { cid: item.item, id: item.id, column: item.column, section: item.section, name: item.name }
75 + });
76 + }
77 + }
78 + }
79 + return detail
80 +}
81 +
82 +export function useReplace () {
83 + let router = useRouter()
84 + function replace (path, query) {
85 + router.replace({
86 + path: path,
87 + query: query
88 + })
89 + }
90 + return replace
91 +}
92 +
93 +export function getScrollTop () {
94 + var scroll_top = 0;
95 + if (document.documentElement && document.documentElement.scrollTop) {
96 + scroll_top = document.documentElement.scrollTop;
97 + }
98 + else if (document.body) {
99 + scroll_top = document.body.scrollTop;
100 + }
101 + return scroll_top;
102 +}
1 +import { mainStore } from '@/utils/generatePackage.js'
2 +
3 +// 删除 keep-alive 缓存
4 +export const store = mainStore();
5 +
6 +export const killPages = () => {
7 + store.changeKeepPages();
8 +}
9 +
10 +export const addPages = () => {
11 + store.keepThisPage();
12 +}
1 <template> 1 <template>
2 - <view class="index"> 2 + <view class="index-page">
3 <nut-config-provider :theme-vars="themeVars"> 3 <nut-config-provider :theme-vars="themeVars">
4 <nut-dialog no-cancel-btn title="温馨提示" content="表单收集量已达到限额,无法再提交数据。" v-model:visible="show_reach_sjsj_max_count" @ok="onOk" /> 4 <nut-dialog no-cancel-btn title="温馨提示" content="表单收集量已达到限额,无法再提交数据。" v-model:visible="show_reach_sjsj_max_count" @ok="onOk" />
5 </nut-config-provider> 5 </nut-config-provider>
...@@ -23,9 +23,6 @@ const themeVars = { ...@@ -23,9 +23,6 @@ const themeVars = {
23 primaryColor: styleColor.baseColor, 23 primaryColor: styleColor.baseColor,
24 }; 24 };
25 25
26 -// web端判断
27 -const is_pc = computed(() => process.env.TARO_ENV === 'h5' && wxInfo().isPC);
28 -
29 const code = getUrlParams(location.href) ? getUrlParams(location.href).code : ''; 26 const code = getUrlParams(location.href) ? getUrlParams(location.href).code : '';
30 const model = getUrlParams(location.href) ? getUrlParams(location.href).model : ''; 27 const model = getUrlParams(location.href) ? getUrlParams(location.href).model : '';
31 28
......
1 <!-- 1 <!--
2 * @Date: 2023-03-24 09:19:27 2 * @Date: 2023-03-24 09:19:27
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2023-03-24 14:21:47 4 + * @LastEditTime: 2023-03-24 15:19:34
5 * @FilePath: /custom_form/src/pages/table/index.vue 5 * @FilePath: /custom_form/src/pages/table/index.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
8 <template> 8 <template>
9 - <div class="">table</div> 9 + <div class="table-page">
10 + <nut-noticebar v-if="formSetting.sjsj_is_count_down" :text="notice_text" :scrollable="true"
11 + :closeMode="true" right-icon="circle-close" />{{ formSetting }}
12 + </div>
10 </template> 13 </template>
11 14
12 <script setup> 15 <script setup>
13 -import wx from 'weixin-js-sdk' 16 +import Taro from '@tarojs/taro'
14 -import { ref, computed } from 'vue' 17 +import { $ } from '@tarojs/extend'
18 +import { createComponentType } from "@/hooks/useComponentType";
19 +import { ref, computed, watchEffect, onMounted } from "vue";
15 import _ from "@/utils/lodash"; 20 import _ from "@/utils/lodash";
16 import { storeToRefs } from 'pinia' 21 import { storeToRefs } from 'pinia'
17 import { mainStore } from '@/stores' 22 import { mainStore } from '@/stores'
...@@ -24,8 +29,10 @@ import { sharePage } from '@/composables/useShare.js' ...@@ -24,8 +29,10 @@ import { sharePage } from '@/composables/useShare.js'
24 // // 获取表单设置 29 // // 获取表单设置
25 const store = mainStore(); 30 const store = mainStore();
26 const { formSetting, formInfo } = storeToRefs(store); 31 const { formSetting, formInfo } = storeToRefs(store);
32 +
27 // web端判断封面图片高度 33 // web端判断封面图片高度
28 const is_pc = computed(() => process.env.TARO_ENV === 'h5' && wxInfo().isPC); 34 const is_pc = computed(() => process.env.TARO_ENV === 'h5' && wxInfo().isPC);
35 +
29 const PHeaderHeight = computed(() => { 36 const PHeaderHeight = computed(() => {
30 if (is_pc.value) { 37 if (is_pc.value) {
31 return "35vh"; 38 return "35vh";
...@@ -38,8 +45,488 @@ const themeVars = { ...@@ -38,8 +45,488 @@ const themeVars = {
38 buttonPrimaryBackground: styleColor.baseColor, 45 buttonPrimaryBackground: styleColor.baseColor,
39 buttonPrimaryBorderColor: styleColor.baseColor, 46 buttonPrimaryBorderColor: styleColor.baseColor,
40 }; 47 };
48 +
49 +const PHeader = ref({});
50 +const PCommit = ref({});
51 +const PHeader_cover = ref("");
52 +const PHeader_title = ref("");
53 +const mockData = ref([]);
54 +const formData = ref([]);
55 +const postData = ref({});
56 +
57 +const form_code = getUrlParams(location.href) ? getUrlParams(location.href).code : '';
58 +// 编辑模式不能提交操作
59 +const model = getUrlParams(location.href) ? getUrlParams(location.href).model : '';
60 +// 模仿金数据的扩展参数
61 +const x_field_1 = getUrlParams(location.href) ? getUrlParams(location.href).x_field_1 : null;
62 +// 周期ID标识
63 +const x_cycle = getUrlParams(location.href) ? getUrlParams(location.href).x_cycle : null;
64 +
65 +// 格式化表单数据结构
66 +const formatData = (data) => {
67 + const arr = [];
68 + data.forEach((field) => {
69 + const { interaction_type, data_type, field_id, field_name, ...component_props } = field;
70 + // 生成组件属性
71 + const temp = {
72 + key: field_name,
73 + value: component_props.default ? component_props.default : "",
74 + component_props,
75 + };
76 + arr.push(temp);
77 + });
78 + return arr;
79 +};
80 +
81 +// 处理没有绑定值的组件的赋值
82 +// 省市区选择,图片上传,文件上传,电子签名,评分组件
83 +const area_picker = ref([]);
84 +const image_uploader = ref([]);
85 +const file_uploader = ref([]);
86 +const sign = ref([]);
87 +const rate_picker = ref([]);
88 +// 动态绑定ref数据
89 +const setRefMap = (el, item) => {
90 + if (el) {
91 + if (item.component_props.tag === "area_picker") {
92 + area_picker.value.push(el);
93 + }
94 + if (item.component_props.tag === "image_uploader") {
95 + image_uploader.value.push(el);
96 + }
97 + if (item.component_props.tag === "file_uploader") {
98 + file_uploader.value.push(el);
99 + }
100 + if (item.component_props.tag === "sign") {
101 + sign.value.push(el);
102 + }
103 + if (item.component_props.tag === "rate_picker") {
104 + rate_picker.value.push(el);
105 + }
106 + }
107 +};
108 +
109 +const notice_text = ref("");
110 +const show = ref(false);
111 +const qr_url = ref("");
112 +const pwd_show = ref(false);
113 +const mmtx_password = ref('');
114 +const form_name = ref('')
115 +
116 +// 提交表单密码
117 +const onSubmitPwd = async () => {
118 + const { code } = await postVerifyPasswordAPI({ form_code, mmtx_password: mmtx_password.value });
119 + if (code) {
120 + pwd_show.value = false;
121 + }
122 +}
123 +
124 +onMounted(async () => {
125 + // TAG: 全局背景色
126 + $('body').css('background-color', styleColor.backgroundColor)
127 + const { data } = await queryFormAPI({ form_code });
128 + const form_data = data;
129 + // 动态修改标题
130 + Taro.setNavigationBarTitle({
131 + title: form_data.name
132 + });
133 + form_name.value = form_data.name;
134 + // 重构数据结构
135 + let page_header = {};
136 + let page_commit = {};
137 + let page_form = [];
138 + form_data.field_list.forEach((element) => {
139 + if (element.tag === "page_header") {
140 + // 页眉组件
141 + page_header = element;
142 + } else if (element.tag === "page_commit") {
143 + // 提交按钮
144 + page_commit = element;
145 + } else {
146 + page_form.push(element);
147 + }
148 + });
149 + /** 页眉属性
150 + * @param label 表单标题
151 + * @param banner_type 页眉类型:["文字", "单张图", "轮播图"] text=文字,image=单张图,carousel=轮播图
152 + * @param banner_url 页眉图片地址
153 + * @param description 描述内容
154 + * @param invisible 页眉展示
155 + */
156 + if (page_header) {
157 + PHeader.value = {
158 + label: page_header.label,
159 + description: page_header.description,
160 + type: page_header.banner_type,
161 + cover: page_header.banner_url,
162 + banner: page_header.banner,
163 + visible: !page_header.invisible,
164 + };
165 + }
166 + if (page_commit) {
167 + PCommit.value = {
168 + text: page_commit.text,
169 + visible: !page_commit.invisible,
170 + };
171 + }
172 + formData.value = formatData(page_form);
173 + mockData.value = [
174 + {
175 + key: "111",
176 + value: "",
177 + component: "",
178 + component_props: {
179 + name: "name",
180 + tag: "name",
181 + label: "姓名",
182 + required: true,
183 + },
184 + },
185 + {
186 + key: "222",
187 + value: "",
188 + component: "",
189 + component_props: {
190 + name: "gender",
191 + tag: "gender",
192 + label: "性别",
193 + default: '男',
194 + required: true,
195 + },
196 + },
197 + ];
198 + // 生成自定义组件
199 + createComponentType(mockData.value);
200 + createComponentType(formData.value);
201 + // 过期时间显示
202 + notice_text.value = `表单报名将在 ${formSetting.value.sjsj_end_time} 后结束`;
203 + // 判断是否需要关注公众号, 弹出二维码识别
204 + if (formSetting.value.wxzq_must_follow && !formSetting.value.x_field_weixin_subscribe) {
205 + show.value = true;
206 + qr_url.value = formSetting.value.wxzq_mp_qrcode;
207 + // 标记用户还未关注
208 + localStorage.setItem('weixin_subscribe', 0);
209 + }
210 + // 判断是否弹出密码输入框
211 + checkUserPassword();
212 + // 启用分享功能,非预览模式
213 + if (formSetting.value.wxzq_is_share && model !== 'preview') {
214 + Taro.ready(() => {
215 + /**
216 + * 微信分享卡片标题模式
217 + * form_name=使用表单名称作为分享标题,customize=自定义分享标题
218 + */
219 + const title = formSetting.value.wxzq_share_title_mode === 'form_name' ? form_name.value : formSetting.value.wxzq_share_custom_title;
220 + // 自定义分享内容
221 + sharePage({ title, desc: formSetting.value.wxzq_share_slogan, imgUrl: formSetting.value.wxzq_share_logo });
222 + });
223 + }
224 +});
225 +
226 +// 打开轮询用户是否关注
227 +const onTap = () => {
228 + if (localStorage.getItem('weixin_subscribe') === '0') {
229 + setInterval(() => {
230 + checkUserSubscribe()
231 + }, 1000);
232 + }
233 +}
234 +
235 +// 检查数据收集设置
236 +const checkUserSubscribe = () => {
237 + // 判断是否需要关注公众号, 弹出二维码识别
238 + if (formSetting.value.wxzq_must_follow && formSetting.value.x_field_weixin_subscribe) {
239 + // 标记用户已关注
240 + localStorage.setItem('weixin_subscribe', 1);
241 + show.value = false;
242 + }
243 + // 凭密码填写设置
244 + if (formSetting.value.mmtx_enable) {
245 + pwd_show.value = true;
246 + } else {
247 + pwd_show.value = false;
248 + }
249 +}
250 +
251 +// 检查密码验证功能
252 +const checkUserPassword = () => {
253 + // 凭密码填写设置
254 + if (formSetting.value.mmtx_enable) {
255 + pwd_show.value = true;
256 + } else {
257 + pwd_show.value = false;
258 + }
259 +}
260 +
261 +// 根据规则隐藏相应字段
262 +const checkRules = () => {
263 + const rule_list = formInfo.value['rule_list'] ? [...formInfo.value['rule_list']] : [];
264 + formData.value.forEach(item => {
265 + // 给受作用的字段绑定判断规则
266 + // 规则失效需要踢出
267 + rule_list.forEach(rule => {
268 + if (rule.field_names?.includes(item.key) && !rule.is_invalid) {
269 + item.field_rules = {
270 + mode: rule.mode,
271 + logical_op: rule.logical_op,
272 + expr_list: rule.expr_list,
273 + }
274 + }
275 + });
276 + // 只检查存在规则的字段
277 + if (item.field_rules) {
278 + let condition = '';
279 + // 多个规则的满足条件,为全且或者全或
280 + const op = item.field_rules?.logical_op === 'AND' ? '&&' : '||';
281 + item.field_rules?.expr_list.forEach(expr => {
282 + if (typeof postData.value[expr['field_name']] === 'string') { // 表单值为字符串(单选,下拉)
283 + const k = !!expr['values'].includes(postData.value[expr['field_name']])
284 + condition += `${k}${op}`
285 + }
286 + if (typeof postData.value[expr['field_name']] === 'object') { // 表单值为数组(多选)
287 + const k = !!(_.intersection(expr['values'], postData.value[expr['field_name']])).length;
288 + condition += `${k}${op}`
289 + }
290 + });
291 + // 把结果转换为布尔值
292 + if (item.field_rules?.logical_op === 'AND') {
293 + if (condition.indexOf('false') >= 0) {
294 + condition = false;
295 + } else {
296 + condition = true;
297 + }
298 + }
299 + if (item.field_rules?.logical_op === 'OR') {
300 + if (condition.indexOf('true') >= 0) {
301 + condition = true;
302 + } else {
303 + condition = false;
304 + }
305 + }
306 + item['component_props']['disabled'] = item.field_rules?.mode === 'SHOW' ? !condition : condition;
307 + }
308 + })
309 +}
310 +
311 +// 操作绑定自定义字段回调
312 +const onActive = (item) => {
313 + if (item.key === "area_picker") {
314 + postData.value[item.filed_name] = item.value;
315 + }
316 + if (item.key === "image_uploader") {
317 + postData.value[item.filed_name] = item.value;
318 + }
319 + if (item.key === "file_uploader") {
320 + postData.value[item.filed_name] = item.value;
321 + }
322 + if (item.key === "sign") {
323 + postData.value[item.filed_name] = item.value;
324 + }
325 + if (item.type === "rate") {
326 + postData.value = _.assign(postData.value, { [item.key]: item.value });
327 + }
328 + if (item.type === "picker") { // 下拉框控件
329 + postData.value = _.assign(postData.value, { [item.key]: item.value });
330 + }
331 + if (item.type === "radio") { // 单选控件
332 + postData.value = _.assign(postData.value, { [item.key]: item.affix ? item.affix : item.value });
333 + }
334 + if (item.type === "checkbox") { // 多选控件
335 + const checkbox_value = _.cloneDeep(item.value)
336 + checkbox_value.forEach((element, index) => {
337 + for (const key in item.affix) {
338 + if (item.affix[key] && element === key) {
339 + checkbox_value[index] = item.affix[key]
340 + }
341 + }
342 + });
343 + postData.value = _.assign(postData.value, { [item.key]: checkbox_value });
344 + }
345 + // 检查规则,会影响字段显示
346 + checkRules();
347 +};
348 +
349 +// 检验没有绑定name的输入项
350 +const validOther = () => {
351 + let valid = {
352 + status: true,
353 + key: "",
354 + };
355 + if (area_picker.value) {
356 + // 省市区地址
357 + area_picker.value.forEach((item, index) => {
358 + if (!area_picker.value[index].validAreaPicker()) {
359 + valid = {
360 + status: area_picker.value[index].validAreaPicker(),
361 + key: "area_picker",
362 + };
363 + return false;
364 + }
365 + });
366 + }
367 + if (image_uploader.value) {
368 + // 图片上传
369 + image_uploader.value.forEach((item, index) => {
370 + if (!image_uploader.value[index].validImageUploader()) {
371 + valid = {
372 + status: image_uploader.value[index].validImageUploader(),
373 + key: "image_uploader",
374 + };
375 + return false;
376 + }
377 + });
378 + }
379 + if (file_uploader.value) {
380 + // 文件上传
381 + file_uploader.value.forEach((item, index) => {
382 + if (!file_uploader.value[index].validFileUploader()) {
383 + valid = {
384 + status: file_uploader.value[index].validFileUploader(),
385 + key: "file_uploader",
386 + };
387 + return false;
388 + }
389 + });
390 + }
391 + if (sign.value) {
392 + // 电子签名
393 + sign.value.forEach((item, index) => {
394 + if (!sign.value[index].validSign()) {
395 + valid = {
396 + status: sign.value[index].validSign(),
397 + key: "sign",
398 + };
399 + return false;
400 + }
401 + });
402 + }
403 + if (rate_picker.value) {
404 + // 评分组件
405 + rate_picker.value.forEach((item, index) => {
406 + if (!rate_picker.value[index].validRate()) {
407 + valid = {
408 + status: rate_picker.value[index].validRate(),
409 + key: "rate_picker",
410 + };
411 + return false;
412 + }
413 + });
414 + }
415 + return valid;
416 +};
417 +
418 +// 预处理表单数据
419 +const preValidData = (values) => {
420 + // 过滤掉标识为 ignore,undefined 的字段数据
421 + let { ignore, undefined, ...rest_data } = values;
422 + // 合并自定义字段到提交表单字段
423 + return _.assign(postData.value, rest_data);
424 +}
425 +
426 +const onSubmit = async (values) => {
427 + // 表单数据处理
428 + postData.value = preValidData(values);
429 + // 合并扩展字段
430 + postData.value = { ...postData.value, x_field_1, x_cycle };
431 + // 检查非表单输入项
432 + if (validOther().status) {
433 + // 编辑模式不能提交数据
434 + if (model === 'edit') {
435 + console.warn(postData.value);
436 + }
437 + if (model === 'edit' || model === 'preview') return false;
438 + // 通过验证
439 + const result = await addFormDataAPI({
440 + form_code,
441 + data: postData.value,
442 + });
443 + if (result.code) {
444 + showSuccessToast("提交成功");
445 + // 缓存表单返回值
446 + store.changeSuccessInfo(result.data);
447 + // 如果类型为跳转网页
448 + if (result.data.commit_action === 'url') {
449 + window.location.href = result.data.commit_url;
450 + } else {
451 + // 跳转成功页面
452 + Taro.navigateTo({
453 + url: '../success/index'
454 + })
455 + }
456 + }
457 + } else {
458 + console.warn(validOther().key + "不通过验证");
459 + // // 图片上传控件报错提示
460 + // if (validOther().key === "image_uploader") {
461 + // showFailToast("图片上传为空");
462 + // }
463 + // // 文件上传控件报错提示
464 + // if (validOther().key === "file_uploader") {
465 + // showFailToast("文件上传为空");
466 + // }
467 + }
468 +};
41 </script> 469 </script>
42 470
43 <style lang="less" scoped> 471 <style lang="less" scoped>
472 +.table-title {
473 + padding: 1rem;
474 + font-size: 1.15rem;
475 + text-align: center;
476 + white-space: pre-wrap;
477 +}
478 +
479 +.table-desc {
480 + padding: 0rem 1rem;
481 + color: #666;
482 + font-size: 0.9rem;
483 + white-space: pre-wrap;
484 +
485 + img {
486 + width: 100%;
487 + }
488 +}
489 +
490 +.table-box {
491 + background-color: #ffffff;
492 + padding-bottom: 1rem;
493 +}
494 +
495 +.wrapper {
496 + display: flex;
497 + align-items: center;
498 + justify-content: center;
499 + height: 100%;
500 +}
501 +
502 +.block {
503 + width: 10rem;
504 + // height: 10rem;
505 + background-color: #fff;
506 +}
507 +
508 +.pwd-wrapper {
509 + display: flex;
510 + align-items: center;
511 + justify-content: center;
512 + height: 100%;
513 +
514 + .block {
515 + width: 80vw;
516 + background-color: #fff;
517 + padding: 1rem;
518 + border-radius: 5px;
519 + }
520 +}
521 +
522 +.PHeader-Text {
523 + padding: 1rem;
524 + font-weight: bold;
525 + white-space: pre;
526 +}
44 527
528 +// :deep(.van-icon) { // 处理正式服务器上箭头上下位移问题
529 +// font-size: var(--van-cell-icon-size);
530 +// line-height: var(--van-cell-line-height);
531 +// }
45 </style> 532 </style>
......
...@@ -1633,6 +1633,11 @@ ...@@ -1633,6 +1633,11 @@
1633 swiper "6.8.0" 1633 swiper "6.8.0"
1634 weui "^1.1.2" 1634 weui "^1.1.2"
1635 1635
1636 +"@tarojs/extend@^3.6.2":
1637 + version "3.6.2"
1638 + resolved "https://mirrors.cloud.tencent.com/npm/@tarojs/extend/-/extend-3.6.2.tgz#f7badc3e322b76b616e10194f030cd5126ce4a6a"
1639 + integrity sha512-lKClhLFk9CPfT2z+djdzX4dBr3KLCzVystjC4KnZ00EAW7j6DlyiudwVUXmPwHAyEkq7n99qKZBy/lWQ3+p+cw==
1640 +
1636 "@tarojs/helper@3.6.2": 1641 "@tarojs/helper@3.6.2":
1637 version "3.6.2" 1642 version "3.6.2"
1638 resolved "https://mirrors.cloud.tencent.com/npm/@tarojs/helper/-/helper-3.6.2.tgz#f1f2e1225a8b3cdfa99ffd62349279350d2c01a2" 1643 resolved "https://mirrors.cloud.tencent.com/npm/@tarojs/helper/-/helper-3.6.2.tgz#f1f2e1225a8b3cdfa99ffd62349279350d2c01a2"
......