123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- <template>
- <div>
- <van-form
- @submit="onSubmit"
- :disabled="formOption.disabled"
- :readonly="formOption.readonly"
- :label-width="formOption.labelWidth"
- :label-align="formOption.labelAlign || 'top'"
- :scroll-to-error="formOption.scroll"
- ref="testForm"
- >
- <van-cell-group inset>
- <div v-for="(i, index) in formConfig" :key="index">
- <van-field
- v-if="i.type == 'input'"
- v-model="formData[i.prop]"
- :label="i.label"
- :name="i.prop"
- :type="i.itemType ? i.itemType : 'text'"
- :placeholder="i.placeholder ? i.placeholder : '请输入'"
- :clearable="i.clearable ? i.clearable : false"
- :rules="getRules(i.prop)"
- >
- </van-field>
- <van-field v-if="i.type == 'switch'" :label="i.label" :name="i.prop">
- <template #input>
- <van-switch v-model="formData[i.prop]" />
- </template>
- </van-field>
- <!-- 多选checkbox -->
- <van-field
- v-if="i.type == 'checkbox'"
- :label="i.label"
- :name="i.prop"
- :rules="getRules(i.prop)"
- >
- <template #input>
- <van-checkbox-group
- v-model="formData[i.prop]"
- direction="horizontal"
- >
- <van-checkbox
- shape="square"
- v-for="j in i.data"
- :key="j.value"
- :name="j.value"
- >{{ j.text }}</van-checkbox
- >
- </van-checkbox-group>
- </template>
- </van-field>
- <!-- 单选radio -->
- <van-field
- v-if="i.type == 'radio'"
- :label="i.label"
- :name="i.prop"
- :rules="getRules(i.prop)"
- >
- <template #input>
- <van-radio-group
- v-model="formData[i.prop]"
- direction="horizontal"
- >
- <van-radio
- v-for="j in i.data"
- :key="j.value"
- :name="j.value || j.id"
- >{{ j.label || j.title }}</van-radio
- >
- </van-radio-group>
- </template>
- </van-field>
- <!-- 单选 -->
- <van-field
- v-if="i.type == 'picker' && i.itemType == 'onePicker'"
- :label="i.label"
- :name="i.prop"
- v-model="formData[i.prop + 'Name']"
- is-link
- readonly
- @click="i.showPicker = true"
- :rules="getRules(i.prop)"
- >
- </van-field>
- <van-popup
- v-model:show="i.showPicker"
- round
- position="bottom"
- v-if="i.type == 'picker' && i.itemType == 'onePicker'"
- >
- <van-picker
- :columns="i.pickerOption.columns"
- @cancel="i.showPicker = false"
- @confirm="(option) => onConfirmPicker(option, i, index)"
- />
- </van-popup>
- <!-- 时间选择器 -->
- <van-field
- v-if="i.type == 'picker' && i.itemType == 'datePicker'"
- :label="i.label"
- :name="i.prop"
- v-model="formData[i.prop]"
- is-link
- readonly
- @click="i.showPicker = true"
- :rules="getRules(i.prop)"
- >
- </van-field>
- <van-popup
- v-model:show="i.showPicker"
- round
- position="bottom"
- v-if="i.type == 'picker' && i.itemType == 'datePicker'"
- >
- <van-date-picker
- @confirm="(option) => onConfirmPicker(option, i, index)"
- @cancel="i.showPicker = false"
- :min-date="i.minDate"
- :max-date="i.maxDate"
- :columns-type="i.columnsType"
- />
- </van-popup>
- <!-- 级联 城市 -->
- <van-field
- v-if="i.type == 'cascader' && i.itemType == 'city'"
- :label="i.label"
- :name="i.prop"
- v-model="formData[i.prop + 'Name']"
- is-link
- readonly
- @click="i.showPicker = true"
- :rules="getRules(i.prop)"
- />
- <van-popup
- v-if="i.type == 'cascader' && i.itemType == 'city'"
- v-model:show="i.showPicker"
- round
- position="bottom"
- >
- <van-cascader
- v-model="formData[i.prop]"
- :title="i.title ? i.title : '请选择'"
- :options="cityOption"
- @close="i.showPicker = false"
- @change="(option) => cityOnChange(option, i, index)"
- @finish="(option) => (i.finishFn ? i.finishFn(option) : () => {})"
- />
- </van-popup>
- <!-- 级联 公共 -->
- <van-field
- v-if="i.type == 'cascader' && i.itemType == 'common'"
- :label="i.label"
- :name="i.prop"
- v-model="formData[i.prop + 'Name']"
- is-link
- readonly
- @click="i.showPicker = true"
- :rules="getRules(i.prop)"
- />
- <van-popup
- v-if="i.type == 'cascader' && i.itemType == 'common'"
- v-model:show="i.showPicker"
- round
- position="bottom"
- >
- <van-cascader
- v-model="formData[i.prop]"
- :title="i.title ? i.title : '请选择'"
- :options="i.options"
- :field-names="i.fieldNames ? i.fieldNames : fieldNames"
- @close="i.showPicker = false"
- @change="(option) => commonOnChange(option, i, index)"
- @finish="
- (option) => (i.finishFn ? i.finishFn(i, option) : () => {})
- "
- />
- <!-- @change="
- (option) => (i.onChangeFn ? i.onChangeFn(i, option) : () => {})
- "
- @finish="
- (option) => (i.finishFn ? i.finishFn(i, option) : () => {})
- " -->
- </van-popup>
- <!-- 插槽 -->
- <van-field v-if="i.type == 'slot'" :label="i.label">
- <template #input>
- <div>
- <slot :name="i.slotName"> {{ i.slotName }}插槽占位符 </slot>
- </div>
- </template>
- </van-field>
- </div>
- </van-cell-group>
- <!-- 循环业务数据 -->
- <van-cell-group
- inset
- v-for="(item, index) in formData[btnConfigCopy.prop]"
- :key="index"
- style="margin-top: 10px !important"
- >
- <div>
- 明细{{ index + 1 }} <span @click="handleRemove(index)">删除</span>
- </div>
- <!-- 循环表单数据 -->
- <div v-for="(i, sonIndex) in btnConfigCopy.listConfig" :key="i.prop">
- <van-field
- v-if="i.type == 'input'"
- v-model="formData[btnConfigCopy.prop][index][i.prop]"
- :label="i.label"
- :name="i.prop"
- :type="i.itemType ? i.itemType : 'text'"
- :placeholder="i.placeholder ? i.placeholder : '请输入'"
- :clearable="i.clearable ? i.clearable : false"
- :rules="getRules(i.prop)"
- >
- </van-field>
- <!-- 单选 -->
- <van-field
- v-if="i.type == 'picker' && i.itemType == 'onePicker'"
- :label="i.label"
- :name="i.prop"
- v-model="formData[btnConfigCopy.prop][index][i.prop + 'Name']"
- is-link
- readonly
- @click="handleListItemClick(i, index, sonIndex)"
- :rules="getRules(i.prop)"
- >
- </van-field>
- <!-- 时间选择器 -->
- <van-field
- v-if="i.type == 'picker' && i.itemType == 'datePicker'"
- :label="i.label"
- :name="i.prop"
- v-model="formData[btnConfigCopy.prop][index][i.prop]"
- is-link
- readonly
- @click="handleListItemClick(i, index, sonIndex)"
- :rules="getRules(i.prop)"
- >
- </van-field>
- </div>
- </van-cell-group>
- <!-- 单独写个循环,保证弹窗唯一 -->
- <div v-for="(item, index) in btnConfigCopy.listConfig" :key="index">
- <van-popup
- v-model:show="item.showPicker"
- round
- position="bottom"
- v-if="item.type == 'picker' && item.itemType == 'onePicker'"
- >
- <van-picker
- :columns="item.pickerOption.columns"
- @cancel="item.showPicker = false"
- @confirm="(option) => onConfirmListPicker(option, item)"
- />
- </van-popup>
- <van-popup
- v-model:show="item.showPicker"
- round
- position="bottom"
- v-if="item.type == 'picker' && item.itemType == 'datePicker'"
- >
- <van-date-picker
- @confirm="(option) => onConfirmListPicker(option, item)"
- @cancel="item.showPicker = false"
- :min-date="item.minDate"
- :max-date="item.maxDate"
- :columns-type="item.columnsType"
- />
- </van-popup>
- </div>
- <!-- 按钮 -->
- <div class="btn-box">
- <van-button
- :plain="btnConfigCopy.plain ? btnConfigCopy.plain : false"
- :type="btnConfigCopy.itemType ? btnConfigCopy.itemType : 'primary'"
- :size="btnConfigCopy.size ? btnConfigCopy.size : 'small'"
- style="width: 100%; border: none"
- @click="handlePush()"
- >
- <template #icon>
- <van-icon
- :name="btnConfigCopy.icon ? btnConfigCopy.icon : 'plus'"
- :size="12"
- />
- </template>
- {{
- btnConfigCopy.btnName ? btnConfigCopy.btnName : "添加"
- }}</van-button
- >
- </div>
- <div style="margin: 16px">
- <van-button round block type="primary" native-type="submit">
- 提交
- </van-button>
- </div>
- </van-form>
- </div>
- </template>
- <script setup>
- import { showLoadingToast, closeToast, showNotify } from "vant";
- import {
- ref,
- getCurrentInstance,
- onMounted,
- reactive,
- computed,
- toRefs,
- watch,
- } from "vue";
- const props = defineProps({
- modelValue: {
- type: Object,
- default: false,
- },
- formConfig: {
- type: Array,
- default: false,
- },
- formOption: {
- type: Object,
- default: false,
- },
- rules: {
- type: Object,
- default: false,
- },
- });
- const proxy = getCurrentInstance().proxy;
- const { formConfig, formOption, rules } = toRefs(props);
- const formData = computed(() => {
- return proxy.modelValue;
- });
- //
- const fieldNames = { text: "label", value: "id" };
- const emit = defineEmits(["update:modelValue"]);
- const onSubmit = () => {
- emit("onSubmit");
- };
- // 获取验证规则
- const getRules = (prop) => {
- if (rules.value.hasOwnProperty(prop) && rules.value[prop]) {
- return rules.value[prop];
- }
- };
- const getReadonly = (i) => {
- return i.readonly ? i.readonly : i.name == "picker" ? true : false;
- };
- // 国家初始化
- const cityOption = ref([]);
- const cityOptionInit = () => {
- proxy.post("/areaInfo/list", formData.value).then((res) => {
- cityOption.value = res.data.map((item, index) => {
- return {
- ...item,
- index: index,
- text: item.chineseName,
- value: item.id,
- children: [],
- };
- });
- });
- };
- let btnConfigCopy = {};
- const btnPickerList = ref([]);
- const formDataInit = () => {
- var map = {
- input: "",
- radio: "",
- switch: false,
- checkbox: [],
- date: "",
- picker: "",
- cascader: "",
- };
- // 判断是否需要按钮
- if (formOption.value.btnConfig && formOption.value.btnConfig.isNeed) {
- formData.value[formOption.value.btnConfig.prop] = [];
- btnConfigCopy = { ...formOption.value.btnConfig };
- if (
- formOption.value.btnConfig.listConfig &&
- formOption.value.btnConfig.listConfig.length > 0
- ) {
- btnPickerList.value = formOption.value.btnConfig.listConfig
- .filter((x) => x.type === "picker")
- .map((x) => x.itemType);
- }
- }
- for (let i = 0; i < formConfig.value.length; i++) {
- const element = formConfig.value[i];
- if (element.type === "slot") {
- continue;
- }
- if (element.type === "cascader" && element.itemType === "city") {
- cityOptionInit();
- }
- if (element.type === "picker" || element.type === "cascader") {
- formData.value[element.prop] = map[element.type];
- formData.value[element.prop + "Name"] = map[element.type];
- }
- if (map[element.type] != undefined) {
- formData.value[element.prop] = map[element.type];
- }
- }
- emit("update:modelValue", formData.value);
- console.log(formData.value, "aws");
- };
- formDataInit();
- // 选择框 确定事件
- const onConfirmPicker = (option, item, index) => {
- switch (item.itemType) {
- case "onePicker": {
- formData.value[item.prop + "Name"] = option.selectedOptions[0].text;
- formData.value[item.prop] = option.selectedOptions[0].value;
- formConfig.value[index].showPicker = false;
- }
- case "datePicker": {
- formData.value[item.prop] = option.selectedValues.join(
- item.split ? item.split : "-"
- );
- formConfig.value[index].showPicker = false;
- }
- }
- };
- const currentIndex = ref(-1);
- const currentSonIndex = ref(-1);
- const handleListItemClick = (i, index, sonIndex) => {
- currentIndex.value = index;
- currentSonIndex.value = sonIndex;
- btnConfigCopy.listConfig[sonIndex].showPicker = true;
- };
- const onConfirmListPicker = (option, item) => {
- switch (item.itemType) {
- case "onePicker": {
- formData.value[btnConfigCopy.prop][currentIndex.value][
- item.prop + "Name"
- ] = option.selectedOptions[0].text;
- formData.value[btnConfigCopy.prop][currentIndex.value][item.prop] =
- option.selectedOptions[0].value;
- btnConfigCopy.listConfig[currentSonIndex.value].showPicker = false;
- }
- case "datePicker": {
- formData.value[btnConfigCopy.prop][currentIndex.value][item.prop] =
- option.selectedValues.join(item.split ? item.split : "-");
- btnConfigCopy.listConfig[currentSonIndex.value].showPicker = false;
- }
- }
- };
- // push事件
- const handlePush = () => {
- if (btnConfigCopy.clickFn && typeof btnConfigCopy.clickFn == "function") {
- btnConfigCopy.clickFn();
- }
- };
- // remove
- const handleRemove = (index) => {
- formData.value[btnConfigCopy.prop].splice(index, 1);
- };
- // 拉去城市最近数据及处理
- const getAreaInfo = (selectedOptions, item, index) => {
- showLoadingToast("加载中...");
- proxy
- .post("/areaInfo/list", { parentId: selectedOptions.value })
- .then((res) => {
- let countryIndex = selectedOptions.selectedOptions[0].index;
- let provinceIndex =
- selectedOptions.tabIndex === 1
- ? selectedOptions.selectedOptions[1].index
- : null;
- let cityIndex =
- selectedOptions.tabIndex === 2
- ? selectedOptions.selectedOptions[2].index
- : null;
- //已经没有下级数据
- if (res.data.length === 0) {
- if (selectedOptions.tabIndex === 1) {
- formData.value[item.prop + "Name"] = selectedOptions.selectedOptions
- .map((item) => item.text)
- .join(" ");
- formConfig.value[index].showPicker = false;
- formData.value.selectedOptions = selectedOptions;
- return;
- }
- }
- if (selectedOptions.tabIndex === 2) {
- formData.value[item.prop + "Name"] = selectedOptions.selectedOptions
- .map((item) => item.text)
- .join(" ");
- formConfig.value[index].showPicker = false;
- formData.value.selectedOptions = selectedOptions;
- return;
- }
- if (selectedOptions.tabIndex === 0) {
- cityOption.value[countryIndex].children = res.data.map(
- (item, index) => {
- return {
- ...item,
- index: index,
- text: item.name,
- value: item.id,
- };
- }
- );
- } else if (selectedOptions.tabIndex === 1) {
- cityOption.value[countryIndex].children[provinceIndex].children =
- res.data.map((item, index) => {
- return {
- ...item,
- index: index,
- text: item.name,
- value: item.id,
- };
- });
- } else if (selectedOptions.tabIndex === 2) {
- cityOption.value[countryIndex].children[provinceIndex].children[
- cityIndex
- ].children = res.data.map((item, index) => {
- return {
- ...item,
- index: index,
- text: item.name,
- value: item.id,
- };
- });
- }
- closeToast();
- });
- };
- // 城市变动事件
- const cityOnChange = (selectedOptions, item, index) => {
- getAreaInfo(selectedOptions, item, index);
- };
- const commonOnChange = ({ selectedOptions }, item, index) => {
- formData.value[item.prop + "Name"] =
- selectedOptions[selectedOptions.length - 1].label;
- };
- const testForm = ref(null); // 延迟使用,因为还没有返回跟挂载
- watch(
- formData.value,
- (val) => {
- emit("update:modelValue", val);
- },
- {
- deep: true,
- }
- );
- </script>
- <style lang="scss" scoped>
- .btn-box {
- margin-top: 10px;
- width: 100%;
- text-align: center;
- }
- </style>
|