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", | ... | ... |
src/components/AreaPickerField/index.vue
0 → 100644
| 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"> *</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> |
src/components/ButtonField/index.vue
0 → 100644
| 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> |
src/components/CalendarField/index.vue
0 → 100644
| 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"> *</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> |
src/components/CheckboxField/index.vue
0 → 100644
| 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"> *</span> | ||
| 12 | + {{ item.component_props.label }} | ||
| 13 | + <span v-if="item.component_props.max" style="color: gray"> | ||
| 14 | + (最多可选数: {{ 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> |
src/components/ContactField/index.vue
0 → 100644
| 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> |
src/components/DatePickerField/index.vue
0 → 100644
| 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"> *</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> |
src/components/DateTimePickerField/index.vue
0 → 100644
| 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"> *</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> |
src/components/DesField/index.vue
0 → 100644
| 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> |
src/components/DividerField/index.vue
0 → 100644
| 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> |
src/components/EmailField/index.vue
0 → 100644
| 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"> *</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> |
src/components/FileUploaderField/index.vue
0 → 100644
| 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"> *</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 }}. {{ file.filename }} {{ (file.size / 1024 / 1024).toFixed(2) }}MB</span> | ||
| 27 | + | ||
| 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> |
src/components/GenderField/index.vue
0 → 100644
| 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"> *</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> |
src/components/IdentityField/index.vue
0 → 100644
| 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"> *</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> |
src/components/ImageUploaderField/index.vue
0 → 100644
| 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"> *</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">上传类型: {{ 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> |
src/components/MarqueeField/index.vue
0 → 100644
| 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> |
src/components/MultiRuleField/index.vue
0 → 100644
| 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"> *</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 }} >> | ||
| 29 | + </div> | ||
| 30 | + <div v-if="rule.desc_type === 'url'" class="rule-box" @click="showUrl(rule)"> | ||
| 31 | + {{ rule.desc_btn_name }} <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 | + >关 闭</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> |
src/components/NameField/index.vue
0 → 100644
| 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"> *</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> |
src/components/NoteField/index.vue
0 → 100644
| 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"> *</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> |
src/components/NumberField/index.vue
0 → 100644
| 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"> *</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> |
src/components/PhoneField/index.vue
0 → 100644
| 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"> *</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> |
src/components/PickerField/index.vue
0 → 100644
| 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"> *</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> |
src/components/RadioField/index.vue
0 → 100644
| 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"> *</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 }} >> | ||
| 25 | + </div> | ||
| 26 | + <div v-if="x.desc_type === 'url'" class="rule-box" @click="showUrl(x)"> | ||
| 27 | + {{ x.desc_btn_name }} <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 | + >关 闭</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> |
src/components/RatePickerField/index.vue
0 → 100644
| 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"> *</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> |
src/components/RuleField/index.vue
0 → 100644
| 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"> *</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 }} >> | ||
| 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 | + >关 闭</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> |
src/components/SignField/index.vue
0 → 100644
| 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"> *</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" /> 点击开始签署电子签名 | ||
| 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> |
src/components/TableField/index.vue
0 → 100644
| 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> |
src/components/TextField/index.vue
0 → 100644
| 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"> *</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> |
src/components/TextareaField/index.vue
0 → 100644
| 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"> *</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> |
src/components/TimePickerField/index.vue
0 → 100644
| 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"> *</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> |
src/components/VideoField/index.vue
0 → 100644
| 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> |
src/components/VideoField/mock.js
0 → 100644
| 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 | +] |
src/components/VideoField/test.vue
0 → 100644
| 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> |
src/hooks/injectionSymbols.js
0 → 100644
src/hooks/useComponentType.js
0 → 100644
| 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 | +} |
src/hooks/useContext.js
0 → 100644
| 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 | +} |
src/hooks/useDebounce.js
0 → 100644
| 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 | +} |
src/hooks/useFlowFn.js
0 → 100644
| 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 | +} |
src/hooks/useGo.js
0 → 100644
| 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 | +} |
src/hooks/useKeepAlive.js
0 → 100644
| 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" | ... | ... |
-
Please register or login to post a comment