vue3动态组件表单,代码简单 扩展性高。

实列;vue3动态组件表单,表格组件 增删改查。 需要更好处理数据 ,自己修改后可分享大家交流学习

动态表单组件form.vue

<template>
    <!-- 动态组件,可以是 div、a-modal、a-drawer 等 -->
    <component :is="toComponent.name" v-bind="toComponent.props">
        <!-- 表单容器 -->
        <a-form ref="formRef" :rules="rules" :model="modelValue" auto-label-width scroll-to-first-error>
            <!-- 表单布局行 -->
            <a-row :gutter="gutter">
                <!-- 动态生成表单项 -->
                <a-col v-for="item in items" :key="getKey(item)" :span="item.span || span">
                    <a-form-item :label="item.label || item.title" :field="getKey(item)">
                        <!-- 动态表单项组件 -->
                        <component :is="getComponent(item)" v-bind="getProps(item)" v-model="modelValue[getKey(item)]">
                            <!-- 动态插槽 -->
                            <template v-for="slot in item.slots" :key="slot.name" #[slot.name]>
                                <div v-html="slot.value"></div>
                            </template>
                        </component>
                        <!-- 帮助文本插槽 -->
                        <template v-if="item.help" #help>{{ item.help }}</template>
                    </a-form-item>
                </a-col>

                <!-- 操作按钮区域(仅在非弹窗模式下显示) -->
                <a-col v-if="component === 'div'" :span="span">
                    <slot>
                        <!-- 默认按钮插槽 -->
                        <component :is="okText === '搜索' ? 'div' : 'a-form-item'">
                            <a-space size="medium">
                                <a-button type="primary" :loading="okLoading" @click="submit">
                                    {{ okText }}
                                </a-button>
                                <a-button @click="reset">重置</a-button>
                            </a-space>
                        </component>
                    </slot>
                </a-col>
            </a-row>

            <!-- 底部自定义插槽 -->
            <slot name="footer"></slot>
        </a-form>
    </component>
</template>

<script setup>
import { ref, computed } from 'vue'

// 定义组件事件
const emits = defineEmits(['submit', 'reset']);

// 双向绑定的表单数据
const modelValue = defineModel()

// 组件属性定义
const props = defineProps({
    formItems: {
        type: Array,
        default: []  // 表单项配置数组
    },
    formRules: {
        type: Object,
        default: null  // 表单验证规则
    },
    defaultValues: {
        type: Object,
        default: null  // 默认值
    },
    gutter: {
        type: Number,
        default: 0  // 栅格间隔
    },
    span: {
        type: Number,
        default: 24  // 表单项默认占据的栅格数
    },
    okText: {
        type: String,
        default: '保存'  // 确认按钮文本
    },
    component: {
        type: [String, Object],
        default: 'div'  // 包裹表单的组件类型
    }
})

// 表单项类型到实际组件的映射
const componentMap = {
    input: 'a-input',           // 输入框
    number: 'a-input-number',   // 数字输入框
    password: 'a-input-password', // 密码框
    radio: 'a-radio-group',     // 单选框组
    checkbox: 'a-checkbox-group', // 多选框组
    textarea: 'a-textarea',     // 文本域
    select: 'a-select',         // 选择器
    treeSelect: 'a-tree-select',      // 树形选择
    rangePicker: 'a-range-picker',      // 时间范围选择器    
    datePicker: 'a-date-picker',      // 日期选择器
    timePicker: 'a-time-picker',      // 时间选择器
    slider: 'a-slider',         // 滑动输入条
    switch: 'a-switch',         // 开关
    editor: 'ai-editor',        // 富文本编辑器
    icon: 'ai-icon',            // 图标选择器
    upfile: 'ai-upload-file',        // 文件上传
    upimage: 'ai-upload-image',      // 图片上传
    dict: 'g-dict',             // 字典选择器
}

// 表单引用
const formRef = ref(null)

// 过滤掉隐藏的表单项
const items = computed(() => props.formItems.filter(m => !m.hidden && m.is_edit !== false))

// 生成表单验证规则
const rules = computed(() => {
    if (props.formRules) return props.formRules
    return items.value.reduce((res, m) => {
        if (m?.is_required === true) {
            res[getKey(m)] = [{
                required: true,
                message: (m.label || m.title) + '不能为空'
            }]
        }
        return res
    }, {})
})

// 获取表单当前值
const values = () => {
    return items.value.reduce((res, m) => {
        res[getKey(m)] = m?.value !== null ? m?.value : undefined
        return res
    }, {})
}

/**
 * 根据表单项配置获取对应的组件
 */
const getComponent = ({ view_type, type }) => {
    const component = view_type || type
    if (component && typeof componentMap[component] === 'string') {
        return componentMap[component]
    }
    return 'a-input'
}

/**
 * 获取表单项的props配置
 */
const getProps = (item) => {
    if (typeof item.props === 'object') return item.props
    return {}
}

/**
 * 获取表单项的标识key
 */
const getKey = (item) => {
    return item.key ?? item.field
}

/**
 * 获取表单默认值
 */
const getDefaultValues = () => {
    const values = {}
    props.formItems.forEach(m => {
        let key = getKey(m)
        values[key] = props.defaultValues?.[key] ?? m.default_value ?? m.value
    })
    return values
}

// 弹窗组件相关状态(用于 modal/drawer 模式)
const popup = ref({
    name: 'a-modal',
    props: {
        visible: false,      // 显示/隐藏
        okLoading: false,    // 确认按钮loading状态
        placement: 'right',  // 抽屉位置
        unmountOnClose: true,// 关闭时卸载组件
        onOk: () => {        // 确认回调
            submit()
        },
        onCancel: () => {    // 取消回调
            if (popup.value.props?.cancelText === '重置') {
                reset()  // 如果取消按钮文本是"重置",则执行重置
            } else {
                close()  // 否则关闭弹窗
            }
        },
    }
})

/**
 * 计算最终要渲染的组件
 */
const toComponent = computed(() => {
    if (typeof props.component === 'string') {
        if (['a-modal', 'a-drawer'].includes(props.component)) {
            // 如果是弹窗组件,返回popup配置
            popup.value.name = props.component
            return popup.value
        }
        return { name: props.component }
    } else {
        // 直接返回组件对象
        return props.component
    }
})

/**
 * 控制loading状态
 */
const okLoading = ref(false)

const load = (bool = true, timer = 1500) => {
    okLoading.value = bool
    popup.value.props.okLoading = bool

    // 自动关闭loading(模拟请求完成)
    bool && setTimeout(() => {
        okLoading.value = false
        popup.value.props.okLoading = false
    }, timer)
}

/**
 * 表单提交方法
 */
const submit = async (callback) => {
    // 表单验证
    const error = await formRef.value?.validate()
    if (error) return

    // 显示loading状态
    load()

    // 执行回调或触发submit事件
    if (typeof callback === 'function') {
        callback(modelValue.value)
    } else {
        emits('submit', modelValue.value)
    }
}

/**
 * 表单重置方法
 */
const reset = (callback) => {
    modelValue.value = { ...getDefaultValues() }

    // 执行回调或触发reset事件
    if (typeof callback === 'function') {
        callback(modelValue.value)
    } else {
        emits('reset', modelValue.value)
    }
}

/**
 * 打开弹窗
 */
const open = (props, visible = true) => {
    Object.assign(popup.value.props, { ...props, visible })
}

/**
 * 关闭弹窗
 */
const close = (props, visible = false) => {
    load(false)
    Object.assign(popup.value.props, { ...props, visible })
}

// 暴露给父组件的方法
defineExpose({
    load,       // 控制loading
    open,       // 打开弹窗
    close,      // 关闭弹窗
    submit,     // 提交表单
    reset,      // 重置表单  
    values,     // 获取默认值
    validate: (...args) => {  // 表单验证
        return formRef.value.validate(...args)
    },
})
</script>
表格组件table.vue 增删改查

<template>
  <a-layout ref="layoutRef" class="a-layout">
    <!-- 搜索框 -->
    <slot name="search" :show="search">
      <a-card v-if="search" ref="searchRef" class="a-search">
        <g-from v-if="form" ref="formRef" ok-text="搜索" v-model="searchData" :form-items="searchItems" :gutter="15"
          :span="6" @submit="onSearch" @reset="onReset" />
      </a-card>
    </slot>

    <a-layout class="a-table">
      <!-- 操作按钮 -->
      <slot name="button" :keys="selectedKeys">
        <g-button v-if="mode?.operate !== 0" :buttons="buttons" :expandAll="expandAll" :hasSearch="search"
          :hasChildren="children" @operate="onOperate" @search="onSearchShow" @expand="onExpandAll"
          @refresh="onRefresh" />
      </slot>

      <!-- 表格列表 -->
      <a-col :style="{ x: '100%', height: scroll.y + 'px', flex: '1' }">
        <a-table :rowKey="rowKey" :data="datas" :scroll="scroll" :loading="loading" v-model:selectedKeys="selectedKeys"
          :expandedKeys="expandedKeys" :rowSelection="rowSelection" :pagination="pagination" :draggable="draggable"
          :bordered="bordered" stickyHeader striped columnResizable @expandedChange="onExpanded"
          @pageChange="pageChange" @pageSizeChange="pageSizeChange" @change="tableChange">
          <template #columns>
            <template v-for="row in columns" :key="row[colKey]">
              <a-table-column v-if="row.is_list !== false" :data-index="row[colKey]" :width="row.width"
                :title="row.title || row.label" :fixed="row.fixed" :tooltip="row.tooltip ?? true" ellipsis
                align="center">
                <template #cell="{ record, rowIndex }">
                  <slot name="cell" :record="record" :rowIndex="rowIndex" :rowKey="rowKey" :colKey="colKey"
                    :column="row">

                    <!-- 可选操作 -->
                    <slot name="link" v-if="row[colKey] === 'optional'">
                      <g-link :rowKey="rowKey" :colKey="colKey" :data="record" :column="row" :links="buttons"
                        @operate="onOptional($event, record)" />
                    </slot>

                    <!-- 显示数据 -->
                    <slot v-else name="show">
                      <g-show :rowKey="rowKey" :colKey="colKey" :data="record" :column="row" @change="handleUpdate" />
                    </slot>

                  </slot>
                </template>
              </a-table-column>
            </template>
          </template>
        </a-table>
      </a-col>
    </a-layout>

    <!-- 表单模态框 -->
    <g-from v-if="form" ref="formRef" v-model="formData" :form-items="formItems" :form-rules="rules"
      :component="component" />

    <!-- 骨架屏 -->
    <g-skeleton v-if="skeleton" />
  </a-layout>
</template>

<script setup>
import { ref, computed, watch } from 'vue'
import gButton from './components/button.vue';
import gLink from './components/link.vue';
import gShow from './components/show.vue';
import gSkeleton from './components/skeleton.vue';
import { Message } from '@arco-design/web-vue';

const emits = defineEmits([
  'operate',
  'query',
  'selected',
  'change',
])

const props = defineProps({
  rowKey: { type: String, default: 'id' },
  colKey: { type: String, default: 'field' },
  data: { type: Array, default: [] },
  columns: { type: Array, default: [] },
  pagination: { type: Object, default: null },
  draggable: { type: Object, default: null },
  rowSelection: { type: Object, default: null },
  bordered: { type: Object, default: { cell: true } },
  buttons: { type: Array, default: [] },
  mode: { type: Object, default: {} },
  skeleton: { type: Boolean, default: false },
  form: { type: [Boolean, Object], default: false },
  rules: { type: Object, default: null },
  api: { type: Object, default: null },
})

const datas = ref([]);
const columns = ref([]);
const queryParams = ref({});
const selectedKeys = ref([]);
const expandedKeys = ref([]);
const expandAll = ref(false);
const search = ref(false);
const children = ref(false);
const loading = ref(false);
const layoutRef = ref(null)

const rowSelection = computed(() => {
  return props.rowSelection ? {
    type: 'checkbox',
    columnWidth: 40,
    showCheckedAll: true,
    onlyCurrent: true,
    fixed: true,
    ...props.rowSelection
  } : null
})

const pagination = computed({
  get: () => {
    return props.pagination ? {
      current: 1,          // 当前页码
      pageSize: 20,        // 每页显示条数
      total: 0,            // 数据总数
      size: "small",       // 分页器大小
      showTotal: true,     // 显示总数统计
      showJumper: true,    // 显示页码跳转
      showPageSize: true,  // 显示页容量切换
      ...props.pagination
    } : false
  },
  set: (val) => {
    props.pagination = val
  }
})

let tht = 0, sht = 0;
const scroll = computed(() => {
  tht = tht > 0 ? tht : layoutRef.value?.$el?.offsetHeight - 120;
  sht = sht > 0 ? sht : searchRef.value?.$el?.clientHeight + 12;
  let y = tht > 0 ? tht : 0;
  if (search.value) {
    y -= sht > 0 ? sht : 0;
  }
  if (pagination.value) {
    y -= 40;
  }
  return { x: '100%', y: y }
})

//-------------------------------监听事件----------------------------------

// 列参数变化
watch(() => props.columns, (val) => {
  columns.value = val
  search.value = val.some(m => m.is_query === true)
}, { immediate: true, deep: true })

// 表格数据变化
watch(() => props.data, (val) => {
  datas.value = val
  children.value = val.some(m => m.children?.length)
  selectedKeys.value = []
  loading.value = false
}, { deep: true })

// 选中变化
watch(() => selectedKeys.value, (val) => {
  emits('selected', val)
})

// 模式变化
watch(() => props.mode, (val) => {
  const bool = columns.value.some(m => m[props.colKey] === 'optional')
  if (!bool && val.optional !== 0) {
    columns.value.push({
      [props.colKey]: 'optional',
      title: '操作',
      fixed: 'right',
      tooltip: false,
      width: val?.colWidth ?? 150,
      is_list: Boolean(val?.optional ?? true),
    })
  }
}, { immediate: true, deep: true })

//-------------------------------处理数据----------------------------------

// 查询数据
const handleQuery = () => {
  loading.value = true
  emits('query', {
    page: pagination?.value?.current,
    pageSize: pagination?.value?.pageSize,
    ...queryParams.value
  })
}

// 更新处理
const handleUpdate = (data) => {
  if (data.id?.length == 0) {
    Message.error('请选择要更新的数据')
    return
  }
  props.api.update(data).then(() => handleQuery())
}

// 删除处理
const handleDelete = (data) => {
  if (data.id?.length == 0) {
    Message.error('请选择要删除的数据')
    return
  }
  props.api.delete(data).then(() => handleQuery())
}

//-------------------------------表格事件----------------------------------

const searchRef = ref(null)
const searchData = ref({})
const searchItems = computed(() => {
  return props.columns.filter(m => m.is_query === true)
    .map(m => ({
      key: m[props.colKey],
      label: m.label ?? m.title,
      type: m.view_type,
      value: m?.value,
      rules: m.rules,
      props: m.props ?? {},
    }))
})

// 搜索数据
const onSearch = (data) => {
  queryParams.value = { ...data };
  if (pagination.value !== false) {
    pagination.value.current = 1
  }
  handleQuery()
}

// 重置搜索
const onReset = () => {
  onSearch()
}

// 刷新数据
const onRefresh = () => {
  queryParams.value = {}
  if (pagination.value !== false) {
    pagination.value.current = 1
  }
  handleQuery()
}

// 显示搜索
const onSearchShow = (val) => {
  search.value = val
}

// 展开状态
const onExpanded = (Keys) => {
  expandedKeys.value = [...Keys]
}

// 展开控制
const onExpandAll = () => {
  loading.value = !expandAll.value
  setTimeout(() => {
    expandedKeys.value = expandAll.value && expandedKeys.value.length
      ? [] : rowKeys(props.data)
    expandAll.value = expandedKeys.value.length > 0
    loading.value = false
  }, 100)
}

// 获取所有行key
const rowKeys = (data) => {
  let res = [];
  data.forEach(m => {
    res.push(m[props.rowKey])
    if (m.children) res = res.concat(rowKeys(m.children))
  });
  return res
}

// 表格按钮
const onOperate = ({ key, data }) => {
  if (!props.api) {
    emits('operate', { key, data })
    return;
  }

  const id = { id: selectedKeys.value }
  switch (key) {
    case 'add':
      openForm(key)
      break
    case 'clear':
      handleDelete(id)
      break
    default:
      if (data?.[key]) {
        handleUpdate({ ...data, ...id })
      } else {
        emits('operate', { key, data })
      }
  }
}

// 可选操作
const onOptional = ({ key, data }, record = null) => {
  if (!props.api) {
    emits('operate', { key, data, record })
    return;
  }

  const id = { id: record?.[props.rowKey] }
  switch (key) {
    case 'save':
      openForm(key, record)
      break
    case 'delete':
      handleDelete(id)
      break
    default:
      if (data?.[key]) {
        handleUpdate({ ...data, ...id })
      } else {
        emits('operate', { key, data, record })
      }
  }
}

//-------------------------------表格数据变化----------------------------------

// 分页变化
const pageChange = (current) => {
  if (pagination.value.current === current) return;
  pagination.value.current = current;
  handleQuery()
}

// 页数量大小变化
const pageSizeChange = (size) => {
  if (pagination.value.pageSize === size) return;
  pagination.value.pageSize = size;
  handleQuery()
}

// 数据变化
const tableChange = (data) => {
  emits('change', data)
}

//-------------------------------表单事件----------------------------------

const action = ref('')
const formRef = ref(null)
const formData = ref({});
const formItems = computed(() => {
  return props.columns.filter(m => m[props.colKey] !== 'optional' && m.is_edit !== false)
    .map(m => ({
      key: m[props.colKey],
      label: m.label ?? m.title,
      type: m.view_type,
      value: m?.value,
      is_edit: m.is_edit,
      is_required: m.is_required,
      help: m?.help,
      rules: m.rules,
      props: m.props ?? {},
    }))
})

// 表单组件
const component = ref({
  name: 'a-modal',
  props: {
    visible: false,
    okLoading: false,
    maskClosable: false,
    unmountOnClose: true,
    onOk: () => {
      formRef.value?.submit(data => {
        component.value.props.okLoading = true
        props.api[action.value]({ ...data, ...props.form?.data })
          .then(() => {
            Object.assign(component.value.props, {
              visible: false,
              okLoading: false
            })
            handleQuery()
          }).catch(err => {
            console.log(err)
          }).finally(() => {
            component.value.props.okLoading = false
          })
      })
    },
    onCancel: () => {
      Object.assign(component.value.props, {
        visible: false,
        okLoading: false
      })
    },
  }
})

// 打开表单
const openForm = (key, data) => {
  action.value = key;
  formRef.value?.reset()
  formData.value = data ?? formRef.value?.values()

  component.value.name = props.mode?.module || 'a-modal'
  Object.assign(component.value.props, {
    width: props.mode?.modalWidth,
    fullscreen: Boolean(props.mode?.fullscreen),
    title: key === 'add' ? '新增' : '编辑',
    visible: true
  })
}

defineExpose()
</script>

<style lang="scss" scoped>
.a-layout {
  height: 100%;
  position: relative;
  overflow: hidden;
}

.a-table {
  padding: 16px;
  background: var(--color-bg-2);
  border-radius: var(--border-radius-small);
  box-sizing: border-box;
  overflow: hidden auto;
}

.a-search {
  margin-bottom: 12px;
  border: none;

  :deep(.arco-card-body) {
    padding: 16px 16px 0;
  }

  :deep(.arco-form-item),
  :deep(.arco-space-item) {
    margin-bottom: 16px;
  }
}

</style>
列数据结构
columns=[
    {
        "key": "PRI",
        "sort": 1,
        "type": "int",
        "field": "id",
        "label": "主键",
        "value": null,
        "width": 180,
        "is_edit": false,
        "is_json": false,
        "is_list": false,
        "is_query": true,
        "is_insert": true,
        "view_type": null,
        "query_type": null,
        "is_required": false
    },
    {
        "key": "MUL",
        "sort": 2,
        "type": "int",
        "field": "user_id",
        "label": "用户ID",
        "value": null,
        "width": 180,
        "is_edit": false,
        "is_json": false,
        "is_list": false,
        "is_query": false,
        "is_insert": true,
        "view_type": null,
        "query_type": null,
        "is_required": false
    },
    {
        "key": "",
        "sort": 3,
        "type": "varchar",
        "field": "title",
        "label": "标题",
        "props": {
            "allowClear": true,
            "placeholder": "请输入标题"
        },
        "value": "",
        "width": 180,
        "is_edit": true,
        "is_json": false,
        "is_list": true,
        "is_query": false,
        "is_insert": true,
        "view_type": "input",
        "query_type": null,
        "is_required": false
    },
    {
        "key": "",
        "sort": 4,
        "type": "varchar",
        "field": "cover",
        "label": "封面",
        "props": {
            "limit": 1,
            "multiple": false
        },
        "value": "",
        "width": 180,
        "is_edit": true,
        "is_json": false,
        "is_list": true,
        "is_query": false,
        "is_insert": true,
        "view_type": "upimage",
        "query_type": null,
        "extend_code": null,
        "extend_type": null,
        "is_required": false
    },
    {
        "key": "MUL",
        "sort": 5,
        "type": "int",
        "field": "category_id",
        "label": "栏目分类",
        "props": {
            "fieldNames": {
                "key": "cid",
                "title": "name",
                "children": "children"
            },
            "options": [
                {
                    "cid": 1,
                    "name": "栏目一"
                },
                {
                    "cid": 2,
                    "name": "栏目二"
                }
            ]
        },
        "value": null,
        "width": 180,
        "is_edit": true,
        "is_json": false,
        "is_list": true,
        "is_query": true,
        "is_insert": true,
        "view_type": "treeSelect",
        "query_type": "eq",
        "extend_code": "category",
        "extend_type": "category",
        "is_required": false
    },
    {
        "key": "",
        "sort": 6,
        "type": "enum",
        "field": "type",
        "label": "发布类型",
        "props": {
            "fieldNames": {
                "label": "label",
                "value": "value"
            },
            "options": [
                {
                    "label": "文章",
                    "value": "text"
                },
                {
                    "label": "视频",
                    "value": "video"
                },
                {
                    "label": "图片",
                    "value": "image"
                }
            ]
        },
        "value": null,
        "width": 180,
        "is_edit": true,
        "is_json": false,
        "is_list": true,
        "is_query": false,
        "is_insert": true,
        "view_type": "radio",
        "query_type": null,
        "extend_code": "article_type",
        "extend_type": "dict",
        "is_required": false
    },
    {
        "key": "",
        "sort": 7,
        "type": "varchar",
        "field": "describe",
        "label": "简介",
        "props": [],
        "value": "",
        "width": 180,
        "is_edit": true,
        "is_json": false,
        "is_list": true,
        "is_query": false,
        "is_insert": true,
        "view_type": "textarea",
        "query_type": null,
        "is_required": false
    },
    {
        "key": "",
        "sort": 8,
        "type": "mediumtext",
        "field": "content",
        "label": "内容",
        "props": [],
        "value": null,
        "width": 180,
        "is_edit": true,
        "is_json": false,
        "is_list": false,
        "is_query": false,
        "is_insert": true,
        "view_type": "editor",
        "query_type": null,
        "extend_code": null,
        "extend_type": null,
        "is_required": false
    },
    {
        "key": "",
        "sort": 9,
        "type": "varchar",
        "field": "images",
        "label": "图片数组",
        "value": null,
        "width": 180,
        "is_edit": false,
        "is_json": false,
        "is_list": false,
        "is_query": false,
        "is_insert": true,
        "view_type": null,
        "query_type": null,
        "is_required": false
    },
    {
        "key": "",
        "sort": 10,
        "type": "varchar",
        "field": "video",
        "label": "视频地址",
        "value": "",
        "width": 180,
        "is_edit": false,
        "is_json": false,
        "is_list": false,
        "is_query": false,
        "is_insert": true,
        "view_type": null,
        "query_type": null,
        "is_required": false
    },
    {
        "key": "",
        "sort": 11,
        "type": "tinyint",
        "field": "vip_level",
        "label": "访问权限",
        "props": {
            "allowClear": true,
            "fieldNames": {
                "label": "label",
                "value": "value"
            },
            "placeholder": "请选择会员",
            "options": [
                {
                    "label": "普通会员",
                    "value": 0
                },
                {
                    "label": "黄金会员",
                    "value": 1
                },
                {
                    "label": "砖石会员",
                    "value": 2
                },
                {
                    "label": "尊贵会员",
                    "value": 3
                }
            ]
        },
        "value": null,
        "width": 180,
        "is_edit": true,
        "is_json": false,
        "is_list": true,
        "is_query": false,
        "is_insert": true,
        "view_type": "select",
        "query_type": null,
        "extend_code": "vip_level",
        "extend_type": "dict",
        "is_required": false
    },
    {
        "key": "",
        "sort": 15,
        "type": "tinyint",
        "field": "is_push",
        "label": "是否推荐",
        "props": {
            "fieldNames": {
                "label": "label",
                "value": "value"
            },
            "options": [
                {
                    "label": "否",
                    "value": 0
                },
                {
                    "label": "是",
                    "value": 1
                }
            ]
        },
        "value": 0,
        "width": 180,
        "is_edit": true,
        "is_json": false,
        "is_list": true,
        "is_query": false,
        "is_insert": true,
        "view_type": "radio",
        "query_type": null,
        "extend_code": "yes_status",
        "extend_type": "dict",
        "is_required": false
    },
]
封装axios request.js

import axios from 'axios'
import router from '@/router'
import { Message } from '@arco-design/web-vue'

const message = (code, msg) => {
    if (!msg?.length) return;
    const msgType = {
        200: 'info',
        201: 'success',
        204: 'warning',
        401: 'warning',
        500: 'error',
    }
    Message[msgType[code] || 'error'](msg);
    if (code === 401) {
        localStorage.removeItem('Token');
        setTimeout(() => {
            router.push({ name: 'login' })
        }, 1500);
    }
}

const baseURL = import.meta.env.PROD ? import.meta.env.VITE_API_BASE_URL : '/api';
const service = axios.create({ baseURL: baseURL, timeout: 5000 })

service.interceptors.request.use(rec => {
    rec.headers.Authorization = localStorage.getItem('Token');
    return rec;
})

service.interceptors.response.use(res => {
    const { code, data, msg } = res.data;
    message(code, msg);
    if (data !== undefined) {
        return data;
    }
}, err => {
    message(500, '网络请求异常,请稍后重试');
    return Promise.reject(err);
})

const request = (options) => {
    options.method = options.method || 'get';
    if (options.method.toLowerCase() === 'get') {
        options.params = options.data;
    }

    const onUploadProgress = options.onUploadProgress;
    if (onUploadProgress && typeof onUploadProgress === 'function') {
        options.onUploadProgress = onUploadProgress;
    }

    return service(options);
}

['get', 'post', 'put', 'delete'].forEach(key => {
    request[key] = (url, data, options) => {
        return request({
            url,
            data,
            method: key,
            ...options,
        })
    }
})

request.Token = () => localStorage.getItem('Token')
request.baseURL = baseURL

export default request;



封装api.js


import request from '@/utils/request'

const methods = {
  get: 'GET',
  post: 'POST',
  list: 'GET',
  read: 'GET',
  add: 'POST',
  save: 'PUT',
  update: 'PUT',
  delete: 'DELETE'
}

const $api = {
  get: (action, data, options = {}) => request({
    url: action,
    method: 'GET',
    data,
    ...options,
  }),
  post: (action, data, options = {}) => request({
    url: action,
    method: 'POST',
    data,
    ...options,
  }),
  upload: (data, onProgress = () => { }, options = {}) => request({
    url: 'upload',
    data,
    method: 'POST',
    onUploadProgress: onProgress,
    ...options,
  }),
  URL: request.baseURL,
  Token: request.Token,
}

$api.set = (obj) => {
  for (let k in obj) {
    if ($api[k]) {
      $api.clear(k)
    }

    $api[k] = {}
    for (let m in methods) {
      if (['get', 'post'].includes(m)) {
        $api[k][m] = (action, data, options) => request({
          url: `${obj[k]}/${action}`,
          method: methods[m],
          data,
          ...options,
        })
      } else {
        $api[k][m] = (data, options) => request({
          url: `${obj[k]}/${m}`,
          method: methods[m],
          data,
          ...options,
        })
      }
    }
  }
  return $api
}

$api.clear = (keys) => {
  if (!keys) {
    for (let k in $api) {
      if (['get', 'post', 'upload'].includes(k)) {
        continue
      }
      delete $api[k]
    }
    return
  }

  else if (typeof keys === 'string') {
    if ($api[keys]) {
      delete $api[keys]
    }
    return
  }

  keys.forEach(k => {
    if ($api[k]) {
      delete $api[k]
    }
  })
}
export default $api


main.js

import { createApp } from 'vue';
import arco from '@arco-design/web-vue';
import arcoIcon from '@arco-design/web-vue/es/icon';
import ueditor from 'vue-ueditor-wrap';
import components from '@/components/web-vue';
import store from './store';
import router from './router';
import api from '@/utils/api'
import App from './App.vue';

import './style.scss';
import '@arco-design/web-vue/dist/arco.css';

const app = createApp(App);
app.provide('api', api);
app.use(router)
    .use(store)
    .use(arco)
    .use(arcoIcon)
    .use(ueditor)
    .use(components)
    .mount('#app');



组件 引用  
import { ref, inject, onMounted } from 'vue'

const $api = inject('api').set({ menu: 'system.menu' })

不需要 卸载组件同时 删除 
 onUnmounted(() => {
    $api.clear('menu')
})

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值