关联表

概述

这个表单和另外的表单的关联关系配置,配置后可以在表单中以子表形式展示关联的表单项,列表不受影响

之前的关联表配置及效果

配置

关联表配置

效果

关联表效果-主表 关联表效果-关联表1 关联表效果-关联表2

现在关联表配置及效果

说明

因为对 online 进行拆分, 并关联表进行了一次升级, 新的关联表使用的也是 online, 有一些新的功能和不同点.

  • 整体
    • 关联字段控制现在不在 关联表配置 tab 页 配置了, 需要到具体的关联表表单中去配置字段隐藏,只读, 并且配置关联字段隐藏的时候, 一对多关联表还需要到外键 tab 页 配置一下, 不然保存不上数据
  • 一对多关联表

    • 列表操作列按钮
    • JS 增强
    • 自定义按钮
    • 支持分页
    • 打开一对多关联表打开表单, 新增和编辑会保留列表的操作列(还会再根据权限过滤),查询项,选择框. 对于详情,默认是保留查询项,保留操作列(但是只有详情按钮)
  • 一对一关联表虽然使用的也是 online,但是获取数据方式还是使用的之前关联表获取数据的,并且一对一关联表只渲染字段部分,隐藏按钮,所以一对一关联表功能没有什么变化

  • 支持关联表指定一个自定义组件路径,之后的页面展示和数据提交逻辑由自定义组件负责.

    • 关联表 配置可以为一个 online 表,这样提交数据会自动插入到 online 表, 可以只是一个唯一标识,到时数据提交自己单独调用接口
    • 关联表 配置为一个 online 表时, 对应定义的 JS 增强生效
    • 对于配置的一对一的关联表的自定义组件, 不需要提交按钮, 只需要定义几个方法,供主表调用即可. 示例文件:src/views/onlineDemo/customLinkTable1.vue
      • add/edit/detail : 这几个方法需要手动调用接口取值(根据关联关系数据 prop)并给表单赋值, return new Promise 或者是个 async 函数,内部的异步操作 都需要使用 await 或者异步操作都完成后使用 resolve()
      • getSubmitValues: 这个方法返回这个关联表提交时候需要的所有数据,提交时候会被调用. 如果想要自己单独调用接口保存数据,直接返回 null 就行
    • 对于配置的一对多关联表的自定义组件, 需要定义几个方法供主表调用.示例文件:src/views/onlineDemo/customLinkTable2.vue
      • initAutoList: 只会在激活时候调用一次, 用于初始化一些数据(比如 online 的 JS 增强),可以是空方法
      • clearDataSource: 清空列表数据, add 时候会调用
      • loadData/loadDataNoPage: 查询数据方法, 需要 async 或 promise, 异步操作都完成后 resolve. 需要根据关联关系 prop 获取数据.
      • 对于列表的新增/编辑/详情操作,如果配置的是 online 表, 可以直接 this.$emit(“add/edit/detail”, {id:xxx,code:xxx}), 否则需要自己开发弹窗和表单页面

配置

新的关联表配置1 新的关联表配置2

效果

新的关联表效果-关联表1 新的关联表效果-关联表1 新的关联表效果-关联表1 新的关联表效果-关联表1

示例代码

位于 src/views/onlineDemo/customLinkTable1.vue 和 src/views/onlineDemo/customLinkTable2.vue 文件中

一对一

<template>
  <div>
    <a-form :form="form">
      <a-form-item label="姓名222">
        <a-input v-decorator="['fd_name']" :disabled="disabledProp" />
      </a-form-item>
      <a-form-item label="年龄">
        <a-input v-decorator="['fd_age']" :disabled="disabledProp" />
      </a-form-item>
    </a-form>
  </div>
</template>

<script>
// 自定义关联表一对一示例
export default {
  name: 'CustomLinkTableOne',
  props: {
    code: {
      type: String,
      default: '',
    },
    inModal: {
      type: Boolean,
      default: false,
    },
    // 关联表关联数据
    linkTableRelationProp: {
      type: Object,
      default: null,
    },
    // 是否展示系统按钮和自定义按钮
    hideFormButton: {
      type: Boolean,
      default: false,
    },
    // 是否禁用表单字段
    disabledProp: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      model: {},
      form: this.$form.createForm(this),
    }
  },
  methods: {
    async add() {
      console.log('自定义一对一 add')
      this.model = {
        fd_name: '',
        fd_age: '',
        id: new Date().getTime(),
      }
      const { id, ...rest } = this.model
      this.form.setFieldsValue(rest)

      // 通知父表单我已加载完毕,之后切换tab就不会再多次调用了, 可不写:这个方法内部的异步操作都需要使用await, 手动写:需要放在异步操作都完成,
      this.$emit('loaded')
    },
    async edit() {
      const { value, linkFieldName } = this.linkTableRelationProp
      console.log('自定义一对一 edit: 外键:', linkFieldName, value)
      // 模拟请求数据, 因为是一对一关系, 所以只需要根据 外键ID 查询
      //   this.model = await getAction('/getDataByForeignKey/' + value)

      this.model = {
        fd_name: '111',
        fd_age: 'sss',
        id: new Date().getTime(),
      }

      const { id: _, ...rest } = this.model
      this.form.setFieldsValue(rest)
      // 通知父表单我已加载完毕,之后切换tab就不会再多次调用了, 可不写:这个方法内部的异步操作都需要使用await, 手动写:需要放在异步操作都完成,
      this.$emit('loaded')
    },
    async detail() {
      const { value, linkFieldName } = this.linkTableRelationProp
      console.log('自定义一对一 detail: 外键:', linkFieldName, value)
      // 模拟请求数据, 因为是一对一关系, 所以只需要根据 外键ID 查询
      //   this.model = await getAction('/getDataByForeignKey/' + value)

      this.model = {
        fd_name: '111',
        fd_age: 'sss',
        id: new Date().getTime(),
      }
      const { id: _, ...rest } = this.model
      this.form.setFieldsValue(rest)
      // 通知父表单我已加载完毕,之后切换tab就不会再多次调用了, 可不写:这个方法内部的异步操作都需要使用await, 手动写:需要放在异步操作都完成,
      this.$emit('loaded')
    },

    // 需要返回这个表的所有数据: id, 表单填写数据, 外键ID的值
    // 如果自己调用接口提交, 这个函数返回个空对象就好
    getSubmitValues() {
      // 表单数据
      const formValues = this.form.getFieldsValue()

      // 赋值关联关系数据
      const { value, linkFieldName } = this.linkTableRelationProp
      const relationData = {
        [linkFieldName]: value,
      }

      return { ...this.model, ...formValues, ...relationData }
    },
  },
}
</script>

<style></style>

一对多

<template>
  <a-card :bordered="false">
    <!-- 查询项 -->
    <div class="table-page-search-wrapper" v-if="!hideSearch">
      <a-form layout="inline" @keyup.enter.native="onSearch">
        <a-row :gutter="24">
          <a-col :span="6">
            <a-form-item label="请输入姓名">
              <a-input v-model="queryParam.fd_name" style="width: 100%"></a-input>
            </a-form-item>
          </a-col>
          <a-col :span="6">
            <a-form-item label="请输入年龄">
              <a-input v-model="queryParam.fd_age" style="width: 100%"></a-input>
            </a-form-item>
          </a-col>

          <!-- 是否固定在新起的一行的右侧 -->
          <a-col :md="6" :sm="8">
            <span :style="{ float: 'left', overflow: 'hidden' }" class="table-page-search-submitButtons">
              <a-button type="primary" @click="onSearch" icon="search">查询</a-button>
            </span>
          </a-col>
        </a-row>
      </a-form>
    </div>

    <div class="table-page-operator-list">
      <!-- 操作按钮区域 -->
      <div class="table-operator" v-if="!hideToolbar">
        <a-button @click="handleAdd" type="primary" icon="plus">新增</a-button>
      </div>

      <!-- 列表 -->
      <div class="list-container">
        <div
          v-if="!hideTableSelect"
          class="ant-alert ant-alert-info"
          style="display: flex; align-items: center; margin-bottom: 16px"
        >
          <i class="anticon anticon-info-circle ant-alert-icon"></i>
          已选择&nbsp;<a style="font-weight: 600">{{ selectedRowKeys.length }}</a
          >&nbsp;&nbsp;
          <a style="margin-left: 24px" @click="clearSelect">清空</a>
        </div>

        <div class="auto-list">
          <a-table
            ref="cgformAutoList"
            bordered
            size="middle"
            rowKey="id"
            :columns="columns"
            :dataSource="dataSource"
            :loading="loading"
            style="min-height: 200px"
          >
            <!-- 操作列 -->
            <a-space slot="action" slot-scope="text, record" v-if="!hideActionColumn || hideActionColumnButShowDetail">
              <a @click.prevent="handleDetail(record)">详情</a>
              <template v-if="!hideActionColumn">
                <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record)">
                  <a style="color: red !important">删除</a>
                </a-popconfirm>
                <a @click.prevent="handleEdit(record)">编辑</a>
              </template>
            </a-space>
          </a-table>
        </div>
      </div>
    </div>
  </a-card>
</template>

<script>
// 自定义关联表一对多示例
export default {
  name: 'CustomLinkTableN',
  props: {
    code: {
      type: String,
      default: '',
    },

    // 是否处于弹窗中(用openCustomModal打开的),影响一些样式
    inModal: {
      type: Boolean,
      default: false,
    },

    // 隐藏表格操作列
    hideActionColumn: {
      type: Boolean,
      default: false,
    },

    // 隐藏表格操作列但是展示详情按钮
    hideActionColumnButShowDetail: {
      type: Boolean,
      default: false,
    },

    // 隐藏表格和表格上方的选择列
    hideTableSelect: {
      type: Boolean,
      default: false,
    },

    // 隐藏查询项
    hideSearch: {
      type: Boolean,
      default: false,
    },

    // 隐藏表格上方的操作按钮
    hideToolbar: {
      type: Boolean,
      default: false,
    },
    // 作为关联表的关联数据
    linkTableRelationProp: {
      type: Object,
      default: null,
    },

    // 自动加载数据
    loadDataOnMount: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      queryParam: {},
      dataSource: [],
      loading: false,
      selectedRowKeys: [],
    }
  },
  computed: {
    columns() {
      const c = [
        {
          title: '姓名',
          dataIndex: 'fd_name',
          align: 'center',
        },
        {
          title: '身份证',
          dataIndex: 'fd_age',
          align: 'center',
        },
      ]
      if (this.hideActionColumn && !this.hideActionColumnButShowDetail) return c
      return [
        ...c,
        {
          title: '操作列',
          key: 'action',
          align: 'center',
          scopedSlots: { customRender: 'action' },
        },
      ]
    },
  },
  methods: {
    handleAdd() {
      // 可以自己写表单, 自己写的时候记得添加数据的时候需要 根据 linkTableRelationProp 关联关系数据给外键字段赋值
      // this.modal.visible=true; xxxx
      // 如果是online, 也可以调用内置的online表单
      this.$emit('add', {
        defaultFormData: null, // 可以添加表单默认值
        code: 'da94102aebae4f6db76d3405a32b288b',
      })
    },
    handleEdit(record) {
      const dataId = record.id
      // 可以自己写表单, 自己写的时候记得添加数据的时候需要 根据 linkTableRelationProp 关联关系数据给外键字段赋值
      // this.modal.visible=true; xxxx
      // 如果是online, 也可以调用内置的online表单
      this.$emit('edit', { id: dataId, code: 'da94102aebae4f6db76d3405a32b288b' })
    },
    handleDetail(record) {
      const dataId = record.id
      // 可以自己写表单, 自己写的时候记得添加数据的时候需要 根据 linkTableRelationProp 关联关系数据给外键字段赋值
      // this.modal.visible=true; xxxx
      // 如果是online, 也可以调用内置的online表单
      this.$emit('detail', { id: dataId, code: 'da94102aebae4f6db76d3405a32b288b' })
    },
    handleDelete() {},
    onSearch() {
      this.loadData()
    },
    clearSelect() {},

    // 下面四个方法是必须的, 然后在loadData/loadDataNoPage方法中触发loaded事件
    async loadData() {
      // 模拟请求数据
      // 因为是一对多, 查询时候需要根据外键查
      const { value, linkFieldName } = this.linkTableRelationProp
      // this.dataSource = await getAction(`/getData?${linkFieldName}=${value}`)

      console.log('自定义一对多加载数据,查询项:', linkFieldName, '=', value)

      this.dataSource = [
        {
          fd_name: '张三111',
          fd_age: '111111xxx',
          id: '1607928908487892994',
        },
        {
          fd_name: '李四22222',
          fd_age: '22222xxxxx',
          id: '1607932603350949890',
        },
      ]
      // 通知父表单我已加载完毕,之后切换tab就不会再多次调用了, 可不写:这个方法内部的异步操作都需要使用await, 手动写:需要放在异步操作都完成,
      this.$emit('loaded')
    },
    loadDataNoPage() {
      // 模拟请求数据
      // 因为是一对多, 查询时候需要根据外键查
      const { value, linkFieldName } = this.linkTableRelationProp
      // this.dataSource = await getAction(`/getData?${linkFieldName}=${value}`)
      console.log('自定义一对多加载数据,查询项:', linkFieldName, '=', value)
      this.dataSource = [
        {
          fd_name: '张三111',
          fd_age: '111111xxx',
          id: '1607928908487892994',
        },
        {
          fd_name: '李四22222',
          fd_age: '22222xxxxx',
          id: '1607932603350949890',
        },
      ]
      // 通知父表单我已加载完毕,之后切换tab就不会再多次调用了, 可不写:这个方法内部的异步操作都需要使用await, 手动写:需要放在异步操作都完成,
      this.$emit('loaded')
    },
    clearDataSource() {
      this.dataSource = []
    },
    // 只会在激活时候调用一次, 用于初始化一些数据(比如online的JS增强),可以是空方法
    async initAutoList() {},
  },
}
</script>

<style></style>

感谢您的反馈。如果您有关于如何使用 KubeSphere 的具体问题,请在 Slack 上提问。如果您想报告问题或提出改进建议,请在 GitHub 存储库中打开问题。