Explorar o código

add anji-plus crud components

木子李·De %!s(int64=4) %!d(string=hai) anos
pai
achega
15b8360a88

+ 38 - 0
report-ui/src/assets/styles/index.scss

@@ -310,3 +310,41 @@ a:hover {
 //   line-height: 30px !important;
 //   height: 30px;
 // }
+
+//自定义表格特殊类型 文字背景
+// 'highway': 'table-primary',
+// 'railway': 'table-success',
+// 'waterway': 'table-info',
+// 'airtransport': 'table-warning',
+// 'multimodal': 'table-danger'
+.table-primary,.table-success,.table-info,.table-warning,.table-danger{
+  border-radius:3px;
+  padding: 2px 5px;
+  border-width: 1px;
+  border-style: solid;
+}
+.table-primary{
+  background: rgba(32, 182, 249, .1);
+  border-color: rgba(32, 182, 249, .2);
+  color: rgb(32, 182, 249);
+}
+.table-success{
+  background: rgba(0, 226, 68, .1);
+  border-color: rgba(0, 226, 68, 0.2);
+  color: rgb(0, 226, 68);
+}
+.table-info{
+  background: rgba(216, 216, 216, .1);
+  border-color: rgba(216, 216, 216, .2);
+  color: rgb(216, 216, 216);
+}
+.table-warning{
+  background: rgba(255, 216, 40, .1);
+  border-color: rgba(255, 216, 40, .2);
+  color: rgb(241, 185, 0);
+}
+.table-danger{
+  background: rgba(249, 32, 32, .1);
+  border-color: rgba(249, 32, 32, .2);
+  color: rgb(249, 32, 32);
+}

+ 661 - 0
report-ui/src/components/AnjiPlus/anji-crud/anji-crud.vue

@@ -0,0 +1,661 @@
+<template>
+  <div :class="[hasTreeFieldInQueryForm ? 'page-container' : 'app-container']">
+    <div v-if="hasTreeFieldInQueryForm"
+         class="left-container">
+      <AnjiTree ref="queryFormTree"
+                v-model.trim="queryParams[queryFormTreeField.field]"
+                :is-open="queryFormTreeField.anjiTreeOption.isOpen"
+                :enable-filter="queryFormTreeField.anjiTreeOption.enableFilter"
+                :label-name="queryFormTreeField.label"
+                :url="queryFormTreeField.anjiTreeOption.url"
+                @node-click="handleTreeNodeCheck" />
+    </div>
+    <div class="right-container">
+      <!-- 查询表单开始 -->
+      <el-form ref="formSearch"
+               :model="queryParams"
+               label-width="100px">
+        <el-row>
+          <el-col v-for="(item, index) in queryFormFieldExcludeTree"
+                  :key="item.field"
+                  :span="queryFormFieldSpan(item)">
+            <el-form-item v-if="index <= 2 || (index > 2 && queryParams.showMoreSearch)"
+                          :label="item.label"
+                          :rules="item.rules"
+                          :prop="item.field">
+              <!-- 输入框 -->
+              <el-input v-if="item.inputType == 'input' || item.inputType == 'input-number'"
+                        v-model.trim="queryParams[item.field]"
+                        :placeholder="item.placeholder || '请输入'"
+                        :clearable="item.clearable !== false"
+                        :disabled="item.disabled"
+                        @change="(value) => queryFormChange(item.field, value)" />
+              <!-- 开关 -->
+              <el-switch v-else-if="item.inputType == 'switch'"
+                         v-model.trim="queryParams[item.field]"
+                         :disabled="item.disabled"
+                         :active-value="item.switchOption.disableValue"
+                         :inactive-value="item.switchOption.enableValue"
+                         active-color="#5887fb"
+                         inactive-color="#ccc"
+                         @change="(value) => queryFormChange(item.field, value)" />
+              <!-- 下拉框 -->
+              <anji-select v-else-if="item.inputType == 'anji-select'"
+                           v-model.trim="queryParams[item.field]"
+                           :multiple="item.anjiSelectOption.multiple"
+                           :dict-code="item.anjiSelectOption.dictCode"
+                           :url="item.anjiSelectOption.url"
+                           :method="item.anjiSelectOption.method"
+                           :query-param="item.anjiSelectOption.queryParam"
+                           :option="item.anjiSelectOption.option"
+                           :label="item.anjiSelectOption.label"
+                           :disabled-options="item.anjiSelectOption.disabledOptions"
+                           :disabled="item.disabled"
+                           :merge-label="item.anjiSelectOption.mergeLabel"
+                           @change="(value) => queryFormChange(item.field, value)" />
+              <!-- 日期时间框  -->
+              <el-date-picker v-else-if="item.inputType.indexOf('date') >= 0"
+                              v-model="queryParams[item.field]"
+                              style="width: 100%"
+                              :placeholder="item.placeholder || '请选择'"
+                              :type="item.inputType"
+                              :clearable="item.clearable !== false"
+                              @change="(value) => queryFormChange(item.field, value)" />
+              <!-- 待扩展的表单类型,请自行扩展 -->
+              <el-input v-else
+                        placeholder="组件不支持此类型表单请至组件内部自行扩展"
+                        disabled />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="6"
+                  style="text-align: center">
+            <el-button type="primary"
+                       @click="handleQueryForm('query')">查询</el-button>
+            <el-button type="danger"
+                       @click="handleResetForm()">重置</el-button>
+            <a style="margin-left: 8px"
+               @click="handleToggleMoreSearch">
+              {{ queryParams.showMoreSearch == true ? '收起' : '展开' }}
+              <i :class="queryParams.showMoreSearch ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" />
+            </a>
+          </el-col>
+        </el-row>
+      </el-form>
+      <!-- 查询表单结束 -->
+
+      <!-- 批量操作 -->
+      <slot name="buttonLeftOnTable" />
+      <el-button v-if="option.buttons.add.isShow == undefined ? true : option.buttons.add.isShow"
+                 v-permission="option.buttons.add.permission"
+                 type="primary"
+                 icon="el-icon-plus"
+                 @click="handleOpenEditView('add')">新增</el-button>
+      <el-button v-if="option.buttons.delete.isShow == undefined ? true : option.buttons.delete.isShow"
+                 v-permission="option.buttons.delete.permission"
+                 :disabled="disableBatchDelete"
+                 type="danger"
+                 icon="el-icon-delete"
+                 @click="handleDeleteBatch()">删除</el-button>
+
+      <!-- 表格开始 -->
+      <el-table :data="records"
+                border
+                @selection-change="handleSelectionChange"
+                @sort-change="handleSortChange">
+        <!--多选-->
+        <el-table-column fixed
+                         type="selection"
+                         width="50"
+                         align="center" />
+        <!--隐藏列-->
+        <el-table-column v-if="tableExpandColumns.length > 0"
+                         type="expand">
+          <template slot-scope="scope">
+            <p v-for="item in tableExpandColumns"
+               :key="item.field"
+               class="table-expand-item">
+              <span class="titel"> {{ item.label }}: </span>
+              <span>{{ scope.row[item.field] }}</span>
+            </p>
+          </template>
+        </el-table-column>
+        <!--序号-->
+        <el-table-column label="序号"
+                         min-width="50"
+                         align="center">
+          <template slot-scope="scope">
+            {{ queryParams.pageSize * (queryParams.pageNumber - 1) + scope.$index + 1 }}
+          </template>
+        </el-table-column>
+
+        <template v-for="item in option.columns">
+          <el-table-column v-if="item.tableHide != true && item.columnType != 'expand'"
+                           :key="item.field"
+                           :prop="item.field"
+                           :label="fieldLabel(item)"
+                           :min-width="item.minWidth || 110"
+                           :sortable="item.sortable"
+                           :show-overflow-tooltip="true"
+                           align="center">
+            <template slot-scope="scope">
+              <div v-if="item.columnType == 'imgPreview'">
+                <!-- 图片缩略图-->
+                <el-image style="width: 25%; height: 50%"
+                          fit="contain"
+                          :src="scope.row[item.field]"
+                          :preview-src-list="[scope.row[item.field]]" />
+              </div>
+              <div v-else>
+                <span v-if="item.inputType == 'switch' && !item.colorStyle">
+                  <el-switch v-model.trim="scope.row[item.field]"
+                             :active-value="1"
+                             :inactive-value="0"
+                             active-color="#5887fb"
+                             inactive-color="#ccc"
+                             @change="switchChange(scope.row, item.switchOption)" />
+                </span>
+                <!-- 带单位 -->
+                <span v-else-if="item.inputType == 'anji-input'">{{ fieldValueByAnjiInput(scope.row[item.field], item) }}</span>
+                <!--表格 a 合并 b上-->
+                <span v-else-if="item.mergeColumn">{{ scope.row[item.field] }}({{ scope.row[item.mergeColumn] }})</span>
+                <!-- 没有单位 -->
+                <span v-else-if="item.colorStyle"
+                      :class="item.colorStyle[scope.row[item.editField]]">{{ fieldValueByRowRenderer(scope.row, item) }}</span>
+                <span v-else>{{ fieldValueByRowRenderer(scope.row, item) }}</span>
+                <!-- 正常展示模式
+                <div v-if="!item.custom">
+                  是第一列数据 && 需要高亮字段不为false 高亮并且可以点击
+                  <span v-if="!index && item.operate !== false" class="view" @click="handleOpenEditView('view', scope.row)">{{ scope.row[item.field] }}</span>
+                  <span v-else>{{ scope.row[item.field] }}</span>
+                </div>
+                -->
+                <!-- 自定义展示数据
+                <div v-else v-html="item.renderer(scope.row)" />-->
+              </div>
+            </template>
+          </el-table-column>
+        </template>
+        <!--操作栏-->
+        <el-table-column align="center"
+                         fixed="right"
+                         label="操作"
+                         :width="option.buttons.customButton && option.buttons.customButton.operationWidth ? option.buttons.customButton.operationWidth : 100">
+          <template slot-scope="scope">
+            <slot name="edit"
+                  :msg="scope.row" />
+            <el-button v-if="option.buttons.edit.isShow == undefined ? true : option.buttons.edit.isShow"
+                       type="text"
+                       size="small"
+                       @click="handleOpenEditView('edit', scope.row)">编辑</el-button>
+            <el-button v-if="hasRowCustomButton == false && option.buttons.delete.isShow == undefined ? true : option.buttons.edit.isShow"
+                       type="text"
+                       size="small"
+                       @click="handleDeleteBatch(scope.row)">删除</el-button>
+            <el-dropdown v-if="hasRowCustomButton"
+                         trigger="click">
+              <span class="el-dropdown-link"> 更多<i class="el-icon-caret-bottom el-icon--right" /> </span>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item class="clearfix">
+                  <slot name="rowButton"
+                        :msg="scope.row" />
+                  <el-button v-if="option.buttons.delete.isShow == undefined ? true : option.buttons.edit.isShow"
+                             type="text"
+                             size="small"
+                             @click="handleDeleteBatch(scope.row)">删除</el-button>
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="pagination">
+        <el-pagination v-show="total > 0"
+                       background
+                       :current-page.sync="queryParams.pageNumber"
+                       :page-sizes="$pageSizeAll"
+                       :page-size="queryParams.pageSize"
+                       layout="total, prev, pager, next, jumper, sizes"
+                       :total="total"
+                       @size-change="handleSizeChange"
+                       @current-change="handleCurrentChange" />
+        <div>
+          <slot name="tableSelectionBtn"
+                :selection="checkRecords" />
+        </div>
+      </div>
+
+      <!-- 表格结束 -->
+
+      <EditDialog ref="edit"
+                  :option="option"
+                  :model-type="editDialogModelType"
+                  :visible="editDialogOpen"
+                  :row-data="editDialogRowData"
+                  @closeEvent="editDialogClosedEvent">
+        <template v-slot:customCard>
+          <slot name="cardInEditPage" />
+        </template>
+        <template slot="editBtn"
+                  slot-scope="scope">
+          <slot name="editBtnPage"
+                :rowData="scope" />
+        </template>
+      </EditDialog>
+    </div>
+    <slot name="pageSection" />
+  </div>
+</template>
+<script>
+import AnjiTree from '@/components/AnjiPlus/anji-tree.vue'
+import EditDialog from './edit'
+import request from '@/utils/request'
+export default {
+  components: {
+    EditDialog,
+    AnjiTree,
+  },
+  props: {
+    option: {
+      require: true,
+      type: Object,
+      default: () => {
+        return {
+          // 查询表单条件
+          queryFormFields: [],
+          // 按钮
+          buttons: {
+            query: {},
+            edit: {},
+            delete: {},
+            add: {},
+          },
+          // 表格列
+          columns: [],
+          queryFormChange: (fileName, val) => { },
+        }
+      },
+    },
+  },
+  data () {
+    return {
+      // 查询表单提交的值
+      queryParams: {
+        showMoreSearch: false, // 是否展开更多搜索条件
+        pageNumber: 1,
+        pageSize: 10,
+        order: '',
+        sort: '',
+      },
+
+      checkRecords: [], // 表格中当前选中的记录
+      records: [], // 接口返回的记录列表
+      total: 0, // 接口返回的总条数
+
+      // 编辑详情弹框
+      editDialogOpen: false, // 新建时主动打开编辑弹框
+      editDialogRowData: {}, // 编辑时的主键
+      editDialogModelType: 'view', // 编辑 查看
+
+      hasRowCustomButton: false, // 除了编辑删除外,还有自定义的行按钮
+    }
+  },
+  computed: {
+    // 左侧树形查询条件
+    queryFormTreeField () {
+      var treeField = this.option.queryFormFields.find((item) => item['inputType'] == 'anji-tree')
+      return treeField
+    },
+    // 查询条件里是否有树形控件
+    hasTreeFieldInQueryForm () {
+      return this.isNotBlank(this.queryFormTreeField)
+    },
+    // 不包含树形控件的查询条件
+    queryFormFieldExcludeTree () {
+      var treeFields = this.option.queryFormFields.filter((item) => item['inputType'] != 'anji-tree')
+      return treeFields
+    },
+    // 主键的列名
+    primaryKeyFieldName () {
+      var primaryKey = this.option.columns.find((item) => item['primaryKey'] == true)
+      if (primaryKey != null) {
+        return primaryKey['field']
+      } else {
+        return null
+        console.warn('在columns中查找primaryKey=true失败,会导致查询详情和删除失败')
+      }
+    },
+
+    // 表格中可展开的列
+    tableExpandColumns () {
+      var expandColumns = this.option.columns.filter((item) => item['columnType'] == 'expand')
+      return expandColumns
+    },
+
+    // 是否可以批量删除
+    disableBatchDelete () {
+      return this.checkRecords.length <= 0
+    },
+  },
+  created () {
+    // 为查询框中所有input加上默认值
+    this.option.queryFormFields.forEach((item) => {
+      // 动态添加属性
+      this.$set(this.queryParams, item.field, item.defaultValue || null)
+    })
+    // 查询列表
+    this.handleQueryForm('query')
+    this.queryFormChange()
+  },
+  mounted () {
+    if (this.$scopedSlots['rowButton'] != null) {
+      this.hasRowCustomButton = true
+    } else {
+      this.hasRowCustomButton = false
+    }
+    console.log(`是否有自定义行按钮: ${this.hasRowCustomButton}`)
+  },
+  methods: {
+    queryFormFieldSpan (item) {
+      // console.log(item)
+
+      if (item.span != null) {
+        return item.span
+      } else {
+        return 6
+      }
+      // let rowLength = this.option.queryFormFields.length;
+      // console.log(rowLength, "ss")
+      // console.log(rowLength % 3)
+      // if (rowLength <= 3) {
+      //   return 6
+      // }
+      // else if (rowLength % 3 == 0) {
+      //   return 8
+      // } else if (rowLength > 6) {
+      //   return 8
+      // }
+    },
+    // 切换更多搜索条件
+    handleToggleMoreSearch () {
+      this.queryParams.showMoreSearch = !this.queryParams.showMoreSearch
+    },
+    // 列上排序切换
+    handleSortChange (column) {
+      // {column: {…}, prop: "orgCode", order: "ascending"}
+      if (column == null || column.prop == null) {
+        console.warn('排序字段名prop为空,无法排序')
+        return
+      }
+      var sort = column.prop // 列表查询默认排序列
+      var order = column.order == 'ascending' ? 'ASC' : 'DESC'
+      this.queryParams['sort'] = sort
+      this.queryParams['order'] = order
+      this.handleQueryForm('query')
+    },
+    // 查询按钮
+    handleQueryForm (from) {
+      // 如果是点查询按钮,把树的查询属性去掉
+      if (from == 'query') {
+        if (this.hasTreeFieldInQueryForm) {
+          delete this.queryParams[this.queryFormTreeField.field]
+        }
+      }
+      // 如果是点树查询,把查询区里的属性去掉
+      if (from == 'tree') {
+        if (this.hasTreeFieldInQueryForm) {
+          var treeVal = this.queryParams[this.queryFormTreeField.field]
+          this.queryParams = {
+            pageNumber: 1,
+            pageSize: 10,
+          }
+          this.queryParams[this.queryFormTreeField.field] = treeVal
+        }
+      }
+      // 默认的排序
+      if (this.isBlank(this.queryParams['order']) && this.isNotBlank(this.option.buttons.query.order)) {
+        this.queryParams['sort'] = this.option.buttons.query.sort
+        this.queryParams['order'] = this.option.buttons.query.order
+      }
+      this.queryParams.pageNumber = 1
+      this.handleQueryPageList()
+    },
+    // 列表查询
+    async handleQueryPageList () {
+      var params = this.queryParams
+      // 将特殊参数值urlcode处理 var params = this.urlEncodeObject(this.queryParams, 'order,sort')
+      const { data, code } = await this.option.buttons.query.api(params)
+      if (code != '200') return
+      this.records = data.records
+      this.total = data.total
+    },
+    // 重置
+    handleResetForm () {
+      this.queryParams = {
+        pageNumber: 1,
+        pageSize: 10,
+      }
+      // this.$refs['queryForm'].resetFields()
+      // this.records = []
+      // this.total = 0
+    },
+    // 树形查询条件点击回调
+    handleTreeNodeCheck () {
+      this.handleQueryForm('tree')
+      // 为新建页面的对应属性值,绑定上对应的默认值
+      var treeFieldName = this.queryFormTreeField['field']
+      for (var i = 0; i < this.option.columns.length; i++) {
+        var item = this.option.columns[i]
+        if (item['editField'] == treeFieldName || item['field'] == treeFieldName) {
+          this.$set(this.option.columns[i], 'defaultValue', this.queryParams[treeFieldName])
+          break
+        }
+      }
+    },
+    // 编辑和查看操作
+    handleOpenEditView (modelType, row) {
+      if (modelType == 'view' || modelType == 'edit') {
+        this.editDialogRowData = row
+      }
+      this.editDialogModelType = modelType
+      if (modelType == 'add') {
+        // 新增模式,不需要查询数据详情,直接打开
+        this.editDialogOpen = true
+      }
+      const obj = {
+        type: modelType,
+        value: row,
+      }
+      this.$emit('handleCustomValue', obj)
+    },
+    // 弹框被关闭时的回调事件
+    editDialogClosedEvent (value) {
+      // 把列表页中弹框打开标记改成已关闭
+      this.editDialogOpen = false
+      // 关闭弹出框时,如果有树,刷新下
+      if (this.hasTreeFieldInQueryForm && this.$refs.queryFormTree != null && !value) {
+        this.$refs.queryFormTree.queryData()
+      }
+      this.handleQueryPageList()
+      // 关闭时 清空表单的验证规则
+      this.$refs.edit.$refs.mainForm.$refs.editForm.resetFields()
+    },
+    // 批量删除
+    handleDeleteBatch (row) {
+      var ids = []
+      if (row != null) {
+        ids.push(row[this.primaryKeyFieldName]) // 删除指定的行
+      } else {
+        // 批量删除选中的行
+        ids = this.checkRecords.map((item) => item[this.primaryKeyFieldName])
+      }
+      this.$confirm('删除确认', '确认要删除吗?', {
+        type: 'warning',
+        confirmButtonClass: 'delete_sure',
+        cancelButtonClass: 'el-button--danger is-plain',
+      }).then(() => {
+        this.option.buttons.delete.api(ids).then((res) => {
+          // {code: "200", message: "操作成功", data: true}
+          this.checkRecords = []
+          // 关闭弹出框时,如果有树,刷新下
+          if (this.hasTreeFieldInQueryForm && this.$refs.queryFormTree != null) {
+            this.$refs.queryFormTree.queryData()
+          }
+          this.handleQueryPageList()
+        })
+      })
+        .catch((e) => {
+          e
+        })
+    },
+
+    // 选择项改变时
+    handleSelectionChange (val) {
+      this.checkRecords = val
+    },
+    // 页码改变
+    handleCurrentChange (pageNumber) {
+      this.queryParams.pageNumber = pageNumber
+      this.handleQueryPageList()
+    },
+    // 每页size改变时
+    handleSizeChange (val) {
+      this.queryParams.pageNumber = 1
+      this.queryParams.pageSize = val
+      this.handleQueryPageList()
+    },
+    // table列文件缩略图
+    thumbnailUrl (row, field) {
+      // return 'http://10.108.3.123:9090/tms/file/download/79ee7e8b-2a9a-4142-b06d-706ac8089205'
+      // if (row.filePath) {
+      //   if (row.filePath.endsWith('xlsx') || row.filePath.endsWith('xls')) {
+      //     return fileExcel;
+      //   } else if (row.filePath.endsWith('pdf')) {
+      //     return filePdf;
+      //   }
+      //   return process.env.VUE_APP_BASE_API + '/tms/file/download/' + row.fileId;
+      // } else {
+      //   return logo;
+      // }
+    },
+    // 带单位的列,需要转换
+    fieldLabel (columnConfig) {
+      if (columnConfig == null) {
+        return ''
+      }
+      if (columnConfig.inputType == 'anji-input' && columnConfig.anjiInput != null) {
+        return `${columnConfig.label}(${columnConfig.anjiInput.unit})`
+      } else {
+        return columnConfig.label
+      }
+    },
+    // 带单位的输入框
+    fieldValueByAnjiInput (value, columnConfig) {
+      if (columnConfig == null) {
+        return value
+      }
+      if (columnConfig.inputType == 'anji-input' && columnConfig.anjiInput != null) {
+        return value / columnConfig.anjiInput.conversion
+      } else {
+        return value
+      }
+    },
+    // 带表格列格式化的值
+    fieldValueByRowRenderer (row, columnConfig) {
+      if (columnConfig == null || typeof columnConfig.fieldTableRowRenderer != 'function') {
+        return row[columnConfig.field]
+      } else {
+        return columnConfig.fieldTableRowRenderer(row)
+      }
+    },
+    // 暴露给外部crud页面,回传saveForm的值
+    getMainEntity () {
+      return this.$refs.edit.getSaveForm()
+    },
+    setMainEntity (object) {
+      this.$refs.edit.setSaveForm(object)
+    },
+    async switchChange (val, api) {
+      request({
+        url: api.url,
+        method: 'put',
+        headers: { noPrompt: false },
+        data: [val.id],
+      }).then((response) => {
+        this.handleQueryPageList()
+      })
+    },
+    queryFormChange (fileName, fieldVal) {
+      if (typeof this.option.queryFormChange == 'function') {
+        this.option.queryFormChange(this.queryParams, fileName, fieldVal)
+      }
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.style-btn {
+  pointer-events: none;
+}
+.page-container {
+  height: 100%;
+  position: relative;
+  .left-container {
+    width: 20%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: #fff;
+    border-radius: 4px 0px 0px 4px;
+    padding: 20px 20px 0;
+    overflow: hidden;
+    overflow-y: auto;
+    height: 100%;
+  }
+  .right-container {
+    width: calc(80% - 5px);
+    position: absolute;
+    top: 0;
+    right: 0;
+    background: #fff;
+    border-radius: 0px 4px 4px 0px;
+    padding: 20px 20px 0;
+    height: 100%;
+  }
+}
+.el-table .cell,
+.el-table td div {
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.el-dropdown {
+  font-size: 12px;
+  display: inline;
+  color: #5887fb;
+  cursor: pointer;
+}
+.el-dropdown-menu--mini .el-dropdown-menu__item {
+  min-width: 80px;
+  max-width: 110px;
+  float: right;
+  .el-button--text {
+    float: right;
+  }
+  &:hover {
+    background: none !important;
+  }
+  .el-button--mini {
+    float: right;
+  }
+  .el-button + .el-button {
+    margin-left: 0 !important;
+    float: right;
+  }
+}
+.pagination {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+</style>

+ 362 - 0
report-ui/src/components/AnjiPlus/anji-crud/edit-form.vue

@@ -0,0 +1,362 @@
+<!--
+ * @Author: lide1202@hotmail.com
+ * @Date: 2021-5-4 11:04:24
+ * @Last Modified by:   lide1202@hotmail.com
+ * @Last Modified time: 2021-5-6 11:04:24
+ !-->
+<template>
+  <div v-if="option['hide'] == null || option['hide'] == false" class="anji-card">
+    <div class="card-head">
+      {{ option.title }}
+      <div class="main-card-header-button">
+        <el-button type="text" @click="handleSetRowColNum(4)">||||</el-button>
+        <el-button type="text" @click="handleSetRowColNum(3)">|||</el-button>
+        <el-button type="text" @click="handleSetRowColNum(2)">||</el-button>
+      </div>
+    </div>
+    <div class="card-body">
+      <el-form ref="editForm" :model="editForm" :label-width="option.labelWidth || '100px'">
+        <!--:disabled="modelType == 'view'"-->
+        <template v-for="group in columnGroups">
+          <el-divider v-if="groupModel" :key="group" content-position="left">{{ group }}</el-divider>
+          <el-row :key="group" class="form_table">
+            <template v-for="item in groupFormFields[group]">
+              <el-col v-if="fieldIsHide(item.editHide) == false" :key="item.editField" :span="item.rowColSpan == null ? cardRowColSpan : item.rowColSpan">
+                <el-form-item :label="item.label" :rules="item.rules" :prop="item.editField" :disabled="item.disabled">
+                  <!-- 输入框 -->
+                  <span v-if="item.tips != '' && item.tips != null" :style="{ 'margin-left': '-13px' }" class="input_tips">
+                    <el-tooltip class="item" effect="dark" :content="item.tips" placement="top-start">
+                      <svg-icon icon-class="tishi-yiwen" />
+                    </el-tooltip>
+                  </span>
+                  <el-input v-if="item.inputType == 'input'" v-model.trim="editForm[item.editField]" :placeholder="item.placeholder || '请输入'" :clearable="item.clearable !== false" :disabled="fieldIsDisable(item.disabled)" @change="(value) => formChange(item.editField, value, null)" />
+                  <!-- 开关 -->
+                  <el-switch v-else-if="item.inputType == 'switch'" v-model.trim="editForm[item.editField]" :disabled="fieldIsDisable(item.disabled)" :active-value="1" :inactive-value="0" inactive-color="#ccc" active-color="#5887fb" @change="(value) => formChange(item.editField, value, null)" />
+                  <el-input-number v-else-if="item.inputType == 'input-number'" v-model.trim="editForm[item.editField]" :min="item.inputNumberOption.min" :max="item.inputNumberOption.max" :placeholder="item.placeholder || '请输入'" :clearable="item.clearable !== false" :disabled="fieldIsDisable(item.disabled)" @change="(value) => formChange(item.editField, value, null)" />
+                  <!-- 自定义input -->
+                  <anji-input v-else-if="item.inputType == 'anji-input'" v-model.trim="editForm[item.editField]" :unit="item.anjiInput.unit" :conversion="item.anjiInput.conversion" :keep-point="item.anjiInput.keepPoint" :rounding="item.anjiInput.rounding" :placeholder="item.placeholder || '请输入'" :clearable="item.clearable !== false" :disabled="fieldIsDisable(item.disabled)" @change="(value) => formChange(item.editField, value, null)" />
+                  <!-- 下拉框 -->
+                  <anji-select
+                    v-else-if="item.inputType == 'anji-select'"
+                    v-model.trim="editForm[item.editField]"
+                    :multiple="item.anjiSelectOption.multiple"
+                    :disabled="fieldIsDisable(item.disabled)"
+                    :dict-code="item.anjiSelectOption.dictCode"
+                    :placeholder="item.placeholder"
+                    :url="item.anjiSelectOption.url"
+                    :method="item.anjiSelectOption.method"
+                    :query-param="item.anjiSelectOption.queryParam"
+                    :merge-label="item.anjiSelectOption.mergeLabel"
+                    :option="item.anjiSelectOption.option"
+                    :label="item.anjiSelectOption.label"
+                    :remote-filter="item.anjiSelectOption.remoteFilter"
+                    :disabled-options="item.anjiSelectOption.disabledOptions"
+                    @change="(value, option) => formChange(item.editField, value, option)"
+                  />
+                  <!-- 日期时间框  -->
+                  <el-date-picker v-else-if="item.inputType.indexOf('date') >= 0" v-model="editForm[item.editField]" style="width: 100%" :placeholder="item.placeholder || '请选择'" :type="item.inputType" :format="item.format" :value-format="item.valueFormat" :disabled="fieldIsDisable(item.disabled)" :clearable="item.clearable !== false" @change="(value) => formChange(item.editField, value, null)" />
+                  <!-- checkbox -->
+                  <anji-checkbox v-else-if="item.inputType == 'checkbox'" v-model.trim="editForm[item.editField]" :dict-code="item.anjiCheckbox.dictCode" :label="item.anjiCheckbox.label" :disabled="fieldIsDisable(item.disabled)" @change="(value, options) => formChange(item.editField, value, options)" />
+                  <!-- 城市三级联动 -->
+                  <anji-cascader v-else-if="item.inputType == 'anji-cascader'" v-model.trim="editForm[item.editField]" :disabled="fieldIsDisable(item.disabled)" :url="item.anjiCascader.url" @change="(value) => formChange(item.editField, value, null)" />
+                  <!-- 上传组件 -->
+                  <anji-upload v-else-if="item.inputType == 'anji-upload'" v-model.trim="editForm[item.editField]" :up-load-url="item.anjiUpload.upLoadUrl" :view-url="item.anjiUpload.viewUrl" :upload-type="item.anjiUpload.uploadType" :limit="item.anjiUpload.limit" @change="(value) => formChange(item.editField, value, null)" />
+                  <!-- input自带输入建议 -->
+                  <anji-autocomplete v-else-if="item.inputType == 'anji-autocomplete'" v-model.trim="editForm[item.editField]" :disabled="fieldIsDisable(item.disabled)" :label="item.anjiAutocomplete.label" :option="item.anjiAutocomplete.option" :appoint-value="item.anjiAutocomplete.appointValue" :url="item.anjiAutocomplete.url" @change="(value, option) => formChange(item.editField, value, option)" />
+                  <!-- textarea -->
+                  <el-input v-else-if="item.inputType == 'textarea'" v-model.trim="editForm[item.editField]" :placeholder="item.placeholder || '请输入'" :clearable="item.clearable !== false" :disabled="fieldIsDisable(item.disabled)" type="textarea" :rows="2" @change="(value) => formChange(item.editField, value, null)" />
+                  <el-input v-else placeholder="组件不支持此类型表单请至组件内部自行扩展" disabled />
+                </el-form-item>
+              </el-col>
+            </template>
+          </el-row>
+        </template>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  components: {},
+  props: {
+    modelType: String, // add view edit
+    showDialog: Boolean,
+    option: {
+      // 界面渲染相关配置json
+      type: [Object],
+      default: () => {
+        return {
+          title: '', // 页面标题
+          labelWidth: '', // 表单输入框label宽度
+          queryFormFields: [], // 查询表单条件
+          buttons: {
+            // 按钮
+            query: {},
+            edit: {},
+            delete: {},
+            add: {},
+          },
+          columns: [], // 表格列
+          formChange: (formData, fieldName, fieldVal, fieldExtend) => {}, // 弹出框表单修改回调
+        }
+      },
+    },
+    // 当relateData不为空时,该组件是渲染与relateData一对一关联的子表信息
+    relateData: {
+      type: [Object],
+      default: () => {
+        return {}
+      },
+    },
+    value: {
+      type: [Object],
+      default: () => {
+        return {}
+      },
+    },
+  },
+  data() {
+    return {
+      cardRowColNum: 2, // 主信息一行显示几列
+      editForm: {}, // 提交表单的数据
+    }
+  },
+  computed: {
+    // 主键的列名
+    primaryKeyFieldName() {
+      var primaryKey = this.option.columns.find((item) => item['primaryKey'] == true)
+      if (primaryKey != null) {
+        return primaryKey['field']
+      } else {
+        return null
+        console.warn('在columns中查找primaryKey=true失败,会导致查询详情和删除失败')
+      }
+    },
+    // 指定当前实体关联主表的
+    joinColumn() {
+      var columnName = this.option.joinColumn
+      if (this.isBlank(columnName)) {
+        console.warn('在joinEntity中查找joinColumn属性失败,会导致查询详情和删除失败')
+        columnName = ''
+      }
+      return columnName
+    },
+    // 提交表单中所有的列,因部分翻译字段,表格中和表单中的key不一样,field editField不全,这里补全下,
+    formFields() {
+      if (this.option.columns == null) {
+        return []
+      }
+      var fields = this.deepClone(this.option.columns)
+      fields = fields.map((item) => {
+        if (this.isBlank(item['editField'])) {
+          item['editField'] = item['field']
+        }
+        // 没有设定分组的,全部补全成 其它信息
+        if (this.isBlank(item['group'])) {
+          item['group'] = '其它'
+        }
+        return item
+      })
+      return fields
+    },
+    // 卡片设定中一行显示几列,每列的col数
+    cardRowColSpan() {
+      return 24 / this.cardRowColNum
+    },
+    // 如果表单内容较多,启用了分组,这里先算出所有的分组
+    columnGroups() {
+      if (this.isBlank(this.formFields)) {
+        return []
+      } else {
+        // 找出所有hide != true的关联表
+        var groups = this.formFields
+          .map((item) => {
+            return item['group']
+          })
+          .filter((currentValue, index, arr) => {
+            return arr.indexOf(currentValue) == index
+          })
+        return groups
+      }
+    },
+    /* {
+      '分组1': [{column1.1}],
+      '分组2': [{column2.1}],
+    }*/
+    groupFormFields() {
+      if (this.showDialog) {
+        // 将每个分组初始化
+        var groupFormFields = {}
+        this.columnGroups.forEach((value, index, array) => {
+          groupFormFields[value] = []
+        })
+        // 将所有编辑列,按分组存放
+        this.formFields.forEach((item, index, array) => {
+          groupFormFields[item['group']].push(item)
+        })
+        return groupFormFields
+      }
+      return []
+    },
+    groupModel() {
+      return this.columnGroups.length > 1
+    },
+  },
+  created() {
+    // 如果表单,是做为主表的编辑页面,如果是通过v-model传递进来的值,通过监听value,更新this.editForm
+    this.$watch(
+      function () {
+        return this.value
+      },
+      function (newVal, oldVal) {
+        this.editForm = newVal
+        // 通过v-model传递值进来时,说明当前form是主表信息
+        this.formChange()
+      }
+    )
+    // 如果表单,是做为子表的编辑页面,当relateData中的关联字段发生更新时,触发查询,比如在goods详情中,goodsCode
+    this.$watch(
+      function () {
+        return this.relateData[this.joinColumn]
+      },
+      function (newVal, oldVal) {
+        // 如果是父组件(弹出框)关闭时,设置this.relateData = {}时触发,清空本组件的数据
+        if (this.isBlank(this.relateData)) {
+          this.cardRowColNum = 2
+          this.editForm = {}
+          return
+        }
+        // 如果是关联字段发生更新,触发查询
+        if (this.isNotBlank(newVal)) {
+          this.queryDetail()
+        }
+      }
+    )
+  },
+  mounted() {
+    // 如果表单,是做为主表的编辑页面,如果是通过v-model传递进来的值
+    if (this.isNotBlank(this.value)) {
+      this.editForm = this.value
+      this.formChange()
+    }
+    // 如果表单,是做为子表的编辑页面,首次打开时,根据关联属性,加载详情数据
+    if (this.isNotBlank(this.relateData) && this.isNotBlank(this.relateData[this.joinColumn])) {
+      this.queryDetail()
+    }
+  },
+  methods: {
+    // 该行是否显示 true/false/ 'hideOnAdd hideOnView hideOnEdit'
+    fieldIsHide(editHide) {
+      if (typeof editHide == 'boolean') {
+        return editHide
+      }
+      if (typeof editHide == 'string') {
+        if (this.modelType == 'add') {
+          return editHide.indexOf('hideOnAdd') >= 0
+        }
+        if (this.modelType == 'view') {
+          return editHide.indexOf('hideOnView') >= 0
+        }
+        if (this.modelType == 'edit') {
+          return editHide.indexOf('hideOnEdit') >= 0
+        }
+      }
+      return false
+    },
+    // 该行是否禁用 true/false/ 'disableOnAdd disableOnView disableOnEdit'
+    fieldIsDisable(disable) {
+      if (typeof disable == 'boolean') {
+        return disable
+      }
+      if (typeof disable == 'string') {
+        if (this.modelType == 'add') {
+          return disable.indexOf('disableOnAdd') >= 0
+        }
+        if (this.modelType == 'view') {
+          return disable.indexOf('disableOnView') >= 0
+        }
+        if (this.modelType == 'edit') {
+          return disable.indexOf('disableOnEdit') >= 0
+        }
+      }
+      return false
+    },
+    // 设置一行显示几列
+    handleSetRowColNum(num) {
+      this.cardRowColNum = num
+      this.$emit('changeRowColNum', num)
+    },
+
+    async queryDetail() {
+      var queryParams = this.relateData
+      const { data, code } = await this.option.buttons.queryByPrimarykey.api(queryParams)
+      if (code != '200') return
+      this.editForm = data
+      this.formChange()
+    },
+    // 校验表单
+    validate(callback) {
+      this.$refs.editForm.validate(async (valid, obj) => {
+        if (callback != null) {
+          callback(valid)
+        }
+      })
+    },
+    handleSave(callback) {
+      this.$refs.editForm.validate(async (valid, obj) => {
+        if (valid) {
+          if (this.modelType == 'add') {
+            // 当edit-from是作为关联子表的界面,补全关联属性
+            if (typeof this.option.beforeInsert == 'function') {
+              this.option.beforeInsert(this.relateData, this.editForm)
+            }
+            const { code, message } = await this.option.buttons.add.api(this.editForm)
+            if (code == '200') {
+              if (callback != null) {
+                callback()
+              }
+            } else {
+              console.log(`提交表单调用新增接口失败:${message}`)
+            }
+          } else {
+            // 当edit-from是作为关联子表的界面,补全关联属性
+            if (typeof this.option.beforeUpdate == 'function') {
+              this.option.beforeUpdate(this.relateData, this.editForm)
+            }
+            const { code, message } = await this.option.buttons.edit.api(this.editForm)
+            if (code == '200') {
+              if (callback != null) {
+                callback()
+              }
+            } else {
+              console.log(`提交表单调用更新接口失败:${message}`)
+            }
+          }
+        } else {
+          console.log('表单校验失败')
+        }
+      })
+    },
+    // 表单任何一个变动时,通知外部v-model
+    formChange(fieldName, fieldVal, fieldExtend) {
+      this.$emit('input', this.editForm)
+      // 表单变动后,回调option中的formChange事件
+      if (typeof this.option.formChange == 'function') {
+        this.option.formChange(this.editForm, fieldName, fieldVal, fieldExtend)
+      }
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.input_tips {
+  position: absolute;
+  margin-top: -8px;
+  .svg-icon {
+    font-size: 20px;
+    color: rgb(71, 8, 8);
+  }
+}
+</style>

+ 498 - 0
report-ui/src/components/AnjiPlus/anji-crud/edit-table.vue

@@ -0,0 +1,498 @@
+<!--
+ * @Author: lide1202@hotmail.com
+ * @Date: 2021-5-4 11:04:24
+ * @Last Modified by:   lide1202@hotmail.com
+ * @Last Modified time: 2021-5-6 11:04:24
+ !-->
+<template>
+  <div v-if="option['hide'] == null || option['hide'] == false"
+       class="anji-card">
+    <div class="card-head">{{ option.title }}</div>
+    <div class="card-body">
+      <el-form ref="form"
+               :model="form">
+        <!-- 表格开始 -->
+        <el-table :data="formRecordsUndelete"
+                  border
+                  :row-class-name="tableRowClassAdapter"
+                  @selection-change="handleSelectionChange"
+                  @row-click="handleTableRowClick">
+          <!-- <el-table-column fixed type="selection" width="50" align="center" /> -->
+
+          <el-table-column label="序号"
+                           min-width="50"
+                           align="center">
+            <template slot-scope="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+
+          <template v-for="item in option.columns">
+            <el-table-column v-if="fieldIsHide(item.tableHide) != true && item.columnType != 'expand'"
+                             :key="item.field"
+                             :label="item.label"
+                             :min-width="item.minWidth || 110"
+                             align="center">
+              <template slot-scope="scope">
+                <el-form-item :prop="'records.' + scope.$index + '.' + item.field"
+                              :rules="item.rules">
+                  <!-- 输入框 -->
+                  <el-input v-if="item.inputType == 'input'"
+                            v-model="scope.row[item.field]"
+                            size="small"
+                            :placeholder="item.placeholder || '请输入'"
+                            :clearable="item.clearable !== false"
+                            :disabled="saveButtonStatus[scope.$index] == 'inShow' || item.disabled"
+                            @change="(value) => tableRowChange(scope.$index, item.field, value)" />
+                  <!-- 开关 -->
+                  <el-switch v-else-if="item.inputType == 'switch'"
+                             v-model="scope.row[item.field]"
+                             :disabled="saveButtonStatus[scope.$index] == 'inShow' || item.disabled"
+                             :active-value="item.switchOption.disableValue"
+                             :inactive-value="item.switchOption.enableValue"
+                             @change="(value) => tableRowChange(scope.$index, item.field, value)"
+                             active-color="#5887fb"
+                             inactive-color="#ccc">
+                  </el-switch>
+                  <el-input v-else-if="item.inputType == 'input-number'"
+                            v-model="scope.row[item.field]"
+                            size="small"
+                            :min="item.inputNumberOption.min"
+                            :max="item.inputNumberOption.max"
+                            :placeholder="item.placeholder || '请输入'"
+                            :clearable="item.clearable !== false"
+                            :disabled="saveButtonStatus[scope.$index] == 'inShow' || item.disabled"
+                            @change="(value) => tableRowChange(scope.$index, item.field, value)" />
+
+                  <!-- 自定义input -->
+                  <anji-input v-else-if="item.inputType == 'anji-input'"
+                              v-model.trim="scope.row[item.field]"
+                              :default-value="item.defaultValue"
+                              :unit="item.anjiInput.unit"
+                              :conversion="item.anjiInput.conversion"
+                              :placeholder="item.placeholder || '请输入'"
+                              :clearable="item.clearable !== false"
+                              :disabled="saveButtonStatus[scope.$index] == 'inShow' || item.disabled"
+                              @change="(value) => tableRowChange(scope.$index, item.field, value)" />
+
+                  <!-- 下拉框 -->
+                  <anji-select v-else-if="item.inputType == 'anji-select'"
+                               v-model.trim="scope.row[item.field]"
+                               :multiple="item.anjiSelectOption.multiple"
+                               :default-value="item.defaultValue"
+                               :dict-code="item.anjiSelectOption.dictCode"
+                               :url="item.anjiSelectOption.url"
+                               :method="item.anjiSelectOption.method"
+                               :query-param="item.anjiSelectOption.queryParam"
+                               :option="item.anjiSelectOption.option"
+                               :label="item.anjiSelectOption.label"
+                               :disabled-options="item.anjiSelectOption.disabledOptions"
+                               :disabled="saveButtonStatus[scope.$index] == 'inShow' || item.disabled"
+                               @change="(value, option) => tableRowChange(scope.$index, item.field, value, option)" />
+                  <!-- 日期时间框  -->
+                  <el-date-picker v-else-if="item.inputType.indexOf('date') >= 0"
+                                  v-model="scope.row[item.field]"
+                                  style="width: 100%"
+                                  :placeholder="item.placeholder || '请选择'"
+                                  :type="item.inputType"
+                                  :clearable="item.clearable !== false"
+                                  :disabled="saveButtonStatus[scope.$index] == 'inShow' || item.disabled"
+                                  @change="(value) => tableRowChange(scope.$index, item.field, value)" />
+                  <!-- 待扩展的表单类型,请自行扩展 -->
+                  <el-input v-else
+                            placeholder="组件不支持此类型表单请至组件内部自行扩展"
+                            disabled />
+                </el-form-item>
+              </template>
+            </el-table-column>
+          </template>
+          <el-table-column fixed="right"
+                           label="操作"
+                           width="100">
+            <template slot-scope="scope">
+              <el-button type="text"
+                         size="small"
+                         @click="handleAddOrUpdate(scope.row, scope.$index)">{{ getRowEditButton(scope.$index) }}</el-button>
+              <el-button type="text"
+                         size="small"
+                         @click="handleDelete(scope.row, scope.$index)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <!-- 表格结束 -->
+      </el-form>
+      <button v-if="modelType != 'view'"
+              class="table-add-row-button"
+              @click="handleAdd">
+        <i class="el-icon-plus" />
+        <span>新增</span>
+      </button>
+    </div>
+  </div>
+</template>
+
+<script>
+const ROW_DELETE_FLAG = 'deletedFlag'
+export default {
+  components: {},
+  props: {
+    modelType: String, // add view edit
+    option: {
+      // 界面渲染相关配置json
+      type: [Object],
+      default: () => {
+        return {
+          title: '', // 页面标题
+          labelWidth: '',
+          queryFormFields: [], // 查询表单条件
+          buttons: {
+            // 按钮
+            query: {},
+            edit: {},
+            delete: {},
+            add: {},
+          },
+          columns: [], // 表格列
+        }
+      },
+    },
+    relateData: {
+      // 关联的主记录
+      type: [Object],
+      default: () => {
+        return {}
+      },
+    },
+    value: {
+      type: [Array],
+      default: () => {
+        return []
+      },
+    },
+    valueNew: {
+      type: [Array],
+      default: () => {
+        return []
+      },
+    }
+  },
+  data () {
+    return {
+      checkRecords: [], // 表格中当前选中的记录
+
+      form: {
+        records: [], // 接口返回的记录列表
+        total: 0, // 接口返回的总条数
+      },
+
+      saveButtonStatus: [], // 维护表格中每行编辑按钮的状态 inShow inEditing inAdding
+
+      rowIdList: []
+    }
+  },
+  computed: {
+    // 主键的列名
+    primaryKeyFieldName () {
+      var primaryKey = this.option.columns.find((item) => item['primaryKey'] == true)
+      if (primaryKey != null) {
+        return primaryKey['field']
+      } else {
+        return null
+        console.warn('在columns中查找primaryKey=true失败,会导致查询详情和删除失败')
+      }
+    },
+    // 指定当前实体关联主表的关联字段,孙子关联表,没有该属性
+    joinColumn () {
+      var columnName = this.option.joinColumn
+      if (this.isBlank(columnName)) {
+        console.warn('在columns中查找关联字段失败,会导致查询详情和删除失败,孙子关联表忽略该错误')
+        columnName = ''
+      }
+      return columnName
+    },
+    // 未删除的记录
+    formRecordsUndelete () {
+      if (this.form.records == null) {
+        return []
+      }
+      return this.form.records.filter((item) => item[ROW_DELETE_FLAG] == null || item[ROW_DELETE_FLAG] == false)
+    },
+  },
+  watch: {},
+  created () {
+    // 主表 relateData 的关联字段 joinColumn 变动时,触发查询子表的查询,孙子关联儿子的无效
+    if (this.isNotBlank(this.joinColumn)) {
+      this.$watch(
+        function () {
+          return this.relateData[this.joinColumn]
+        },
+        function (newVal, oldVal) {
+          // 如果是关联字段发生更新,触发查询
+          if (this.isNotBlank(newVal)) {
+            this.handleQueryPageList(newVal)
+          } else {
+            // 如果关联字段为空,清空本表格的数据,如果是父组件(弹出框)关闭时,设置this.relateData = {默认值}时触发
+            this.checkRecords = []
+            this.form.records = []
+            this.form.total = 0
+            this.saveButtonStatus = []
+          }
+        }
+      )
+    }
+  },
+  mounted () {
+    // 首次打开时,根据主表关联字段查询子表,加载表格数据
+    if (this.isNotBlank(this.relateData) && this.isNotBlank(this.relateData[this.joinColumn])) {
+      this.handleQueryPageList()
+    }
+  },
+  methods: {
+    // 该行是否显示 true/false/ 'hideOnAdd hideOnView hideOnEdit'
+    fieldIsHide (tableHide) {
+      if (typeof tableHide == 'boolean') {
+        return tableHide
+      }
+      if (typeof tableHide == 'string') {
+        if (this.modelType == 'add') {
+          return tableHide.indexOf('hideOnAdd') >= 0
+        }
+        if (this.modelType == 'view') {
+          return tableHide.indexOf('hideOnView') >= 0
+        }
+        if (this.modelType == 'edit') {
+          return tableHide.indexOf('hideOnEdit') >= 0
+        }
+      }
+      return false
+    },
+    // 获取行的提交按钮文字
+    getRowEditButton (index) {
+      if (this.saveButtonStatus[index] == 'inEditing') {
+        return 'btn_savetemp'
+      } else if (this.saveButtonStatus[index] == 'inAdding') {
+        return 'btn_savetemp'
+      } else if (this.saveButtonStatus[index] == 'inShow') {
+        return 'btn_edit'
+      } else {
+        return 'not_permission'
+      }
+    },
+    // 表格行渲染前前置处理
+    tableRowClassAdapter ({ row, rowIndex }) {
+      row.index = rowIndex
+    },
+    // 查询
+    async handleQueryPageList (joinColumnValue) {
+      if (this.isBlank(joinColumnValue)) {
+        joinColumnValue = this.relateData[this.joinColumn]
+      }
+      var params = {}
+      params[this.joinColumn] = joinColumnValue
+      this.queryPageList(params)
+    },
+    // 暴露给外部直接调用带参数
+    async queryPageList (params) {
+      // 默认的排序
+      if (this.isNotBlank(this.option.buttons.query.order)) {
+        params['sort'] = this.option.buttons.query.sort
+        params['order'] = this.option.buttons.query.order
+      }
+      const { data, code } = await this.option.buttons.query.api(params)
+      if (code != '200') return
+      this.form.records = data.records
+      this.form.total = data.total
+      this.$emit('input', this.form.records)
+      for (var i = 0; i < this.form.total; i++) {
+        this.saveButtonStatus.push('inShow')
+      }
+    },
+    // 选择项改变时
+    handleSelectionChange (val) {
+      this.checkRecords = val
+    },
+    // 表格选中某一行时
+    handleTableRowClick (row, column, event) {
+      // console.log(row)
+      // console.log(column)
+      // 行点击后,回调option中的tableRowClick事件
+      if (typeof this.option.tableRowClick == 'function') {
+        this.option.tableRowClick(this.form.records, row, row.index, this.relateData)
+      }
+    },
+    // 行数据更新时回调
+    tableRowChange (rowIndex, fieldName, fieldVal, fieldExtend) {
+      // 通知外面的组件
+      this.$emit('input', this.form.records)
+
+      // 表单变动后,回调option中的tableRowChange事件
+      if (typeof this.option.tableChange == 'function') {
+        this.option.tableChange(this.form.records, rowIndex, fieldName, fieldVal, fieldExtend, this.relateData)
+      }
+    },
+    // 新增
+    handleAdd () {
+      this.saveButtonStatus.push('inAdding')
+      this.form.records.push({})
+    },
+    // 父节点Change,子节点表格更新
+    handleUpdata () {
+      this.saveButtonStatus = []
+      this.form.records = []
+    },
+    // 提交和修改
+    handleAddOrUpdate (row, index) {
+      // 编辑状态下点击保存提交
+      if (this.saveButtonStatus[index] == 'inEditing' || this.saveButtonStatus[index] == 'inAdding') {
+        this.handleSaveTemp(row, index)
+      } else {
+        this.$set(this.saveButtonStatus, index, 'inEditing')
+      }
+    },
+    // 校验表单
+    validate (callback) {
+      this.$refs['form'].validate(async (valid, obj) => {
+        if (callback != null) {
+          callback(valid)
+        }
+      })
+    },
+    // 暂存
+    async handleSaveTemp (row, index) {
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          if (this.isBlank(row[this.primaryKeyFieldName])) {
+            // 补全关联属性
+            if (typeof this.option.beforeInsert == 'function') {
+              this.option.beforeInsert(this.relateData, row)
+            }
+          } else {
+            // 补全关联属性
+            if (typeof this.option.beforeUpdate == 'function') {
+              this.option.beforeUpdate(this.relateData, row)
+            }
+          }
+          // 将行按钮的文字改成编辑
+          this.$set(this.saveButtonStatus, index, 'inShow')
+          // 通知外面的组件
+          this.$emit('input', this.form.records)
+        }
+      })
+      /*
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          // 验证通过
+          if (this.isBlank(row[this.primaryKeyFieldName])) {
+            // 补全关联属性
+            if (typeof this.option.beforeInsert == 'function') {
+              this.option.beforeInsert(this.relateData, row)
+            }
+            // 主键为空,新增
+            this.option.buttons.add.api(row).then((response) => {
+              if (response.code == 200) {
+                this.$set(this.saveButtonStatus, index, 'inShow')
+                // 保存完成后刷新列表
+                this.handleQueryPageList()
+              }
+            })
+          } else {
+            // 补全关联属性
+            if (typeof this.option.beforeUpdate == 'function') {
+              this.option.beforeUpdate(this.relateData, row)
+            }
+            // 修改
+            this.option.buttons.edit.api(row).then((response) => {
+              if (response.code == 200) {
+                this.$set(this.saveButtonStatus, index, 'inShow')
+                // 保存完成后刷新列表
+                this.handleQueryPageList()
+              }
+            })
+          }
+        } else {
+          console.log('valid fail')
+          return false
+        }
+      })
+      */
+    },
+    // 删除
+    handleDelete (row, index) {
+      this.saveButtonStatus.splice(index, 1) // 清空状态
+      // 界面上临时新增出来的一行,还没有提交到数据库,可以直接删除
+      if (this.saveButtonStatus[index] == 'inAdding') {
+        this.form.records.splice(index, 1)
+        this.saveButtonStatus.splice(index, 1)
+        this.$emit('input', this.form.records)
+        return
+      }
+      // if (this.isBlank(row) || this.isBlank(row[this.primaryKeyFieldName])) {
+      //   return
+      // }
+      // 将对应的行标识成删除
+      // 找出该行在原始记录中的index
+      // var realIndex = this.form.records.findIndex((item) => item[this.primaryKeyFieldName] == row[this.primaryKeyFieldName])
+      // row[ROW_DELETE_FLAG] = true
+      // this.$set(this.form.records, realIndex, row)
+      // this.$emit('input', this.form.records)
+      this.form.records.splice(index, 1)
+      this.rowIdList.push(row.id)
+      this.$emit('input', this.form.records)
+      this.$emit('update:valueNew', this.rowIdList)
+      /*
+      之前是直接调用接口删除,后面统一改成在主表接口中增加、更新、删除
+      // 已经保存在数据库的的行,要调用删除按钮
+      var primaryKey = row[this.primaryKeyFieldName]
+      this.$confirm(this.$lang('promptMessage_deleteTip'), this.$lang('promptMessage_deleteTipTitle'), {
+        type: 'warning',
+        confirmButtonClass: 'delete_sure',
+        cancelButtonClass: 'el-button--danger is-plain',
+      })
+        .then(() => {
+          this.option.buttons.delete.api(primaryKey).then((res) => {
+            // {code: "200", message: "操作成功", data: true}
+            this.checkRecords = []
+            this.handleQueryPageList()
+          })
+        })
+        .catch((e) => {
+          e
+        })
+        */
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.table-add-row-button {
+  width: 100%;
+  margin-top: 0px;
+  margin-bottom: 0px;
+  border-color: #d9d9d9;
+  border-style: dashed;
+  line-height: 1.499;
+  position: relative;
+  display: inline-block;
+  font-weight: 400;
+  white-space: nowrap;
+  text-align: center;
+  background-image: none;
+  border: 1px solid transparent;
+  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+  -webkit-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  -ms-touch-action: manipulation;
+  touch-action: manipulation;
+  height: 32px;
+  padding: 0 15px;
+  font-size: 14px;
+  border-radius: 4px;
+  color: rgba(0, 0, 0, 0.65);
+  background-color: #fff;
+  border-color: #d9d9d9;
+}
+</style>

+ 386 - 0
report-ui/src/components/AnjiPlus/anji-crud/edit.vue

@@ -0,0 +1,386 @@
+<!--
+ * @Author: lide1202@hotmail.com
+ * @Date: 2021-3-13 11:04:24
+ * @Last Modified by:   lide1202@hotmail.com
+ * @Last Modified time: 2021-3-13 11:04:24
+ !-->
+<template>
+  <el-dialog
+    :width="dialogWidth"
+    :class="dialogFullScreen?'full-screen':'notfull-screen'"
+    :close-on-click-modal="false"
+    center
+    :visible.sync="showDialog"
+    :fullscreen="dialogFullScreen"
+    @close="handleCloseDialog('close')"
+  >
+    <template v-slot:title>
+      {{ option.title + '--' + modelType }}
+      <button
+        type="button"
+        aria-label="Close"
+        class="el-dialog__headerbtn"
+        style="right: 50px"
+        @click="dialogFullScreen = !dialogFullScreen"
+      >
+        <i class="el-dialog__close el-icon el-icon-full-screen" />
+      </button>
+    </template>
+
+    <!--主表详情信息-->
+    <component
+      :is="'EditForm'"
+      ref="mainForm"
+      v-model="saveForm"
+      :option="option"
+      :model-type="modelType"
+      :show-dialog="showDialog"
+      @changeRowColNum="handleSetRowColNum"
+    />
+
+    <!--关联表相关-->
+    <template v-for="(item, index) in joinEntitys">
+      <component
+        :is="getComponentByJoinType(item.joinType, item)"
+        :ref="'joinForm' + index"
+        :key="index"
+        v-model="saveForm[item.fieldNameInMainEntityOnSave]"
+        :value-new.sync="saveForm[item.fieldNameInMainEntityOnId]"
+        :option="item"
+        :model-type="modelType"
+        :relate-data="saveForm"
+      />
+
+      <!--孙子的关联表-->
+      <template v-for="(grandsonItem, grandsonIndex) in item.joinEntitys">
+        <component
+          :is="getComponentByJoinType(grandsonItem.joinType, grandsonItem)"
+          ref="grandsonForm"
+          :key="index + '.' + grandsonIndex"
+          :option="grandsonItem"
+          :model-type="modelType"
+          :relate-data="saveForm"
+        />
+      </template>
+    </template>
+
+    <!--自定义的卡片插槽-->
+    <slot name="customCard" />
+
+    <div
+      slot="footer"
+      style="text-align: center"
+    >
+      <slot
+        v-if="modelType =='edit'"
+        name="editBtn"
+        :rowData="rowData"
+      />
+      <el-button
+        type="danger"
+        plain
+        @click="handleCloseDialog('close')"
+      >关闭</el-button>
+      <el-button
+        v-if="modelType!='view'"
+        type="primary"
+        plain
+        @click="handleValidateAndSave"
+      >保存</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import EditForm from './edit-form'
+import EditTable from './edit-table'
+export default {
+  name: 'EditDialog',
+  components: { EditForm, EditTable },
+  props: {
+    visible: {
+      type: [Boolean],
+      default: () => {
+        return false
+      },
+    },
+    rowData: {
+      // 查询参数,对应表格那行数据
+      type: [Object],
+      default: () => {
+        return {}
+      },
+    },
+    modelType: String, // add view edit
+    option: {
+      require: true,
+      type: Object,
+      default: () => {
+        return {
+          title: '', // 页面标题
+          labelWidth: '', // 表单输入框label宽度
+          queryFormFields: [], // 查询表单条件
+          buttons: {
+            // 按钮
+            query: {},
+            edit: {},
+            delete: {},
+            add: {},
+          },
+          columns: [], // 表格列
+        }
+      },
+    },
+  },
+  data() {
+    return {
+      showDialog: false, // 编辑详情弹框是否显示
+      dialogFullScreen: false, // 弹出框全屏
+      cardRowColNum: 2, // 主信息一行显示几列
+
+      // 提交表单的数据
+      saveForm: {},
+      // 已成功校验的关联表单个数
+      countForValidJoinForm: 0,
+    }
+  },
+  computed: {
+    // 弹出框的宽度,根据一行显示几列动态调整
+    dialogWidth() {
+      if (this.cardRowColNum == 2) {
+        return '60%'
+      }
+      if (this.cardRowColNum == 3) {
+        return '70%'
+      }
+      if (this.cardRowColNum == 4) {
+        return '80%'
+      }
+      return '60%'
+    },
+    // 关联属性表
+    joinEntitys() {
+      if (this.isBlank(this.option.joinEntitys)) {
+        return []
+      } else {
+        return this.option.joinEntitys
+        // 找出所有hide != true的关联表
+        // var entitys = this.option.joinEntitys.filter((item) => item['hide'] == null || item['hide'] == false)
+        // return entitys
+      }
+    },
+    // 一对一关联表的个数
+    countJoinEntityOneToOne() {
+      var entitys = this.joinEntitys.filter((item) => item['joinType'] == 'OneToOne')
+      if (entitys == null) {
+        return 0
+      }
+      return entitys.length
+    },
+  },
+  watch: {
+    // 监控dialog的显示隐藏变量
+    visible(newValue, oldValue) {
+      this.showDialog = newValue
+      // 为主表的编辑表单,渲染上默认值
+      this.initDefaultSaveForm()
+    },
+    rowData(newValue, oldValue) {
+      if (newValue != null) {
+        this.queryByPrimarykey(newValue)
+      }
+    },
+  },
+  mounted() {
+    // 为主表的编辑表单,渲染上默认值
+    this.initDefaultSaveForm()
+  },
+  methods: {
+    // 暴露给外部crud页面,回传saveForm的值
+    getSaveForm() {
+      return this.saveForm
+    },
+    setSaveForm(saveForm) {
+      this.saveForm = saveForm
+    },
+    initDefaultSaveForm() {
+      // saveForm的默认值
+      var defaultSaveForm = {}
+      this.option.columns.forEach((item) => {
+        var key = item.editField
+        if (this.isBlank(key)) {
+          key = item.field
+        }
+        var val = item.defaultValue
+        if (this.isNotBlank(val)) {
+          defaultSaveForm[key] = val
+        }
+      })
+      // 为主表的编辑表单,渲染上默认值
+      this.saveForm = this.deepClone(defaultSaveForm)
+      console.log('编辑框默认值:' + JSON.stringify(this.saveForm))
+    },
+    handleCloseDialog(val) {
+      // 为主表的编辑表单,渲染上默认值
+      this.initDefaultSaveForm()
+      this.showDialog = false, // 编辑详情弹框是否显示
+      this.dialogFullScreen = false, // 弹出框全屏
+      this.cardRowColNum = 2, // 主信息一行显示几列
+      this.countForValidJoinForm = 0, // 已成功校验的关联表单个数
+      this.$emit('closeEvent', val)
+    },
+    // 设置一行显示几列
+    handleSetRowColNum(num) {
+      this.cardRowColNum = num
+    },
+    // 根据关联类型计算组件
+    getComponentByJoinType(type, item) {
+      if (type == 'OneToOne') {
+        return 'EditForm'
+      } else if (type == 'OneToMany') {
+        return 'EditTable'
+      } else {
+        return ''
+      }
+    },
+    async queryByPrimarykey(rowData) {
+      const { data, code } = await this.option.buttons.queryByPrimarykey.api(rowData)
+      if (code != '200') return
+      this.showDialog = true
+      this.saveForm = data
+    },
+    // 保存前,先调用校验
+    handleValidateAndSave() {
+      this.countForValidJoinForm = 0
+      // 主表单校验
+      this.$refs.mainForm.validate((mainValid) => {
+        if (mainValid == false) {
+          console.warn('主表单校验失败')
+          return
+        }
+        console.log('主表单校验完成')
+        if (this.joinEntitys == null || this.joinEntitys.length == 0) {
+          // 如果子表没有信息,直接提交
+          this.handleSave()
+          return
+        }
+        for (var i = 0; i < this.joinEntitys.length; i++) {
+          console.log(`开始校验子表单-${i} 校验`)
+          var item = this.joinEntitys[i]
+          if (this.$refs['joinForm' + i] == null || item.hide == true || this.saveForm[item.fieldNameInMainEntityOnSave] == null || this.saveForm[item.fieldNameInMainEntityOnSave].length == 0) {
+            console.log('子表单没有数据,直接跳过')
+            this.countForValidJoinForm++
+            console.log('已经校验的子表单:' + this.countForValidJoinForm + ' 共:' + this.joinEntitys.length)
+            // 所有关联表单校验通过
+            if (this.countForValidJoinForm == this.joinEntitys.length) {
+              console.log('子表单校验完成,提交主表单')
+              this.handleSave()
+            }
+            continue
+          }
+          var joinForm = this.$refs['joinForm' + i]
+          if (toString.call(joinForm) == '[object Array]') {
+            joinForm = joinForm[0]
+          }
+          joinForm.validate((joinValid) => {
+            if (joinValid) {
+              this.countForValidJoinForm++
+              console.log('已经校验的子表单:' + this.countForValidJoinForm + ' 共:' + this.joinEntitys.length)
+              // 所有关联表单校验通过
+              if (this.countForValidJoinForm == this.joinEntitys.length) {
+                console.log('子表单校验完成,提交主表单')
+                this.handleSave()
+              }
+            } else {
+              console.warn(`子表单${i}校验失败:`)
+            }
+          })
+        }
+      })
+    },
+    async handleSave() {
+      // 新增
+      if (this.modelType == 'add') {
+        const { code, message } = await this.option.buttons.add.api(this.saveForm)
+        if (code == '200') {
+          // 保存结束,关闭对话框
+          this.handleCloseDialog()
+          // 向外层发关闭事件
+          this.$emit('closeEvent')
+          return
+        } else {
+          this.countForValidJoinForm = 0, // 已成功校验的关联表单个数
+          console.log(`提交表单调用新增接口失败:${message}`)
+        }
+      }
+      // 修改
+      if (this.modelType == 'edit') {
+        // console.log(this.$slots.customCard[0].context.$refs.cardInEditData.updateData.settingValue)
+        const { code, message } = await this.option.buttons.edit.api(this.saveForm)
+        if (code == '200') {
+          // 保存结束,关闭对话框
+          this.handleCloseDialog()
+          // 向外层发关闭事件
+          this.$emit('closeEvent')
+          return
+        } else {
+          this.countForValidJoinForm = 0, // 已成功校验的关联表单个数
+          console.log(`提交表单调用更新接口失败:${message}`)
+        }
+      }
+      /* 分步提交
+      // 提交主表
+      this.$refs.mainForm.handleSave(() => {
+        // 如果没有一对一的关联表,直接关闭对话框
+        if (this.countJoinEntityOneToOne == 0) {
+          // 保存结束,关闭对话框
+          this.handleCloseDialog()
+          // 向外层发关闭事件
+          this.$emit('closeEvent')
+          return
+        }
+        // 主表保存成功后,保存子表
+        for (var i = 0; i < this.joinEntitys.length; i++) {
+          if (this.joinEntitys[i].joinType == 'OneToOne') {
+            this.$refs['joinForm' + i].handleSave(() => {
+              this.countForSavedOneToOneJoinEntity++
+              if (this.countForSavedOneToOneJoinEntity == this.countJoinEntityOneToOne) {
+                // 保存结束,关闭对话框
+                this.handleCloseDialog()
+                // 向外层发关闭事件
+                this.$emit('closeEvent')
+              }
+            })
+          }
+        }
+      })*/
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+// /deep/.el-dialog__body {
+//   background-color: rgb(240, 242, 245);
+//   padding: 5px;
+//   max-height: 60vh;
+//   overflow: auto;
+//   height: calc(100vh - 100px);
+// }
+.notfull-screen {
+  /deep/.el-dialog__body {
+    background-color: rgb(240, 242, 245);
+    padding: 5px;
+    max-height: 60vh;
+    overflow: auto;
+  }
+}
+.full-screen {
+  /deep/.el-dialog__body {
+    background-color: rgb(240, 242, 245);
+    padding: 5px;
+    height: calc(100vh - 110px);
+    overflow: auto;
+  }
+}
+</style>

+ 13 - 0
report-ui/src/main.js

@@ -23,8 +23,17 @@ import 'echarts/lib/component/tooltip'
 // import 'echarts-gl'
 Vue.component('v-chart', ECharts)
 
+// anji component
+import anjiCrud from '@/components/AnjiPlus/anji-crud/anji-crud'
+import anjiSelect from '@/components/AnjiPlus/anji-select'
+Vue.component('anji-crud', anjiCrud)
+Vue.component('anji-select', anjiSelect)
+
 // permission control
 import '@/permission'
+// 按钮权限的指令
+import permission from '@/components/Permission/index'
+Vue.use(permission)
 
 import Avue from '@smallwei/avue';
 import '@smallwei/avue/lib/index.css';
@@ -44,6 +53,10 @@ Object.keys(filter).forEach(key => {
 // register global mixins.
 Vue.mixin(mixins)
 
+
+// 分页的全局size配置;
+Vue.prototype.$pageSizeAll = [10, 50, 100, 200, 500]
+
 Vue.config.productionTip = false
 
 // create the app instance.

+ 103 - 13
report-ui/src/mixins/common.js

@@ -1,3 +1,4 @@
+import Cookies from 'js-cookie'
 export default {
   data () {
     return {
@@ -5,11 +6,11 @@ export default {
   },
   computed: {
     // 网页高度
-    bodyWidth() {
+    bodyWidth () {
       return document.body.clientWidth
     },
     // 网页宽度
-    bodyHeight() {
+    bodyHeight () {
       return document.body.clientHeight
     },
   },
@@ -20,6 +21,12 @@ export default {
   destroyed () {
   },
   methods: {
+    setCookies (key, val, option) {
+      if (option == null) {
+        option = { expires: 15 }
+      }
+      Cookies.set(key, val, option)
+    },
     goBack () {
       this.$router.go(-1)
     },
@@ -95,7 +102,7 @@ export default {
       }
     },
     // 获取对象类型
-    getObjectType(obj) {
+    getObjectType (obj) {
       var toString = Object.prototype.toString
       var map = {
         '[object Boolean]': 'boolean',
@@ -114,21 +121,41 @@ export default {
       }
       return map[toString.call(obj)]
     },
-    isNumber(obj) {
+    isNumber (obj) {
       return this.getObjectType(obj) == 'number'
     },
-    isString(obj) {
+    isString (obj) {
       return this.getObjectType(obj) == 'string'
     },
-
-    hasOwn(obj, key) {
+    isArray (obj) {
+      return this.getObjectType(obj) == 'array'
+    },
+    hasOwn (obj, key) {
       return Object.prototype.hasOwnProperty.call(obj, key)
     },
 
-    isNotNull(val) {
+    isNotBlank (val) {
+      return !this.isBlank(val)
+    },
+    isBlank (val) {
+      if (this.isNull(val)) {
+        return true
+      }
+      if (typeof val === 'string') {
+        return val.trim() == ''
+      }
+      if (typeof val === 'object') {
+        for (var key in val) {
+          return false
+        }
+        return true
+      }
+      return false
+    },
+    isNotNull (val) {
       return !this.isNull(val)
     },
-    isNull(val) {
+    isNull (val) {
       // 特殊判断
       if (val && parseInt(val) === 0) return false
       const list = ['$parent']
@@ -154,7 +181,7 @@ export default {
     },
 
     // 对象深拷贝
-    deepClone(data) {
+    deepClone (data) {
       var type = this.getObjectType(data)
       var obj
       if (type === 'array') {
@@ -190,7 +217,7 @@ export default {
     },
 
     // 合并json
-    mergeObject() {
+    mergeObject () {
       var target = arguments[0] || {}
       var deep = false
       var arr = Array.prototype.slice.call(arguments)
@@ -233,7 +260,7 @@ export default {
     },
 
     // 获取dom在屏幕中的top和left
-    getDomTopLeftById(id) {
+    getDomTopLeftById (id) {
       var dom = document.getElementById(id)
       var top = 0
       var left = 0
@@ -243,7 +270,7 @@ export default {
       }
       return { top: top, left: left }
     },
-    objToOne(obj) {
+    objToOne (obj) {
       var tmpData = {}
       for (var index in obj) {
         if (typeof obj[index] == 'object') {
@@ -255,5 +282,68 @@ export default {
       }
       return tmpData
     },
+    urlEncode (val) {
+      return encodeURIComponent(val)
+    },
+    urlDecode (val) {
+      return decodeURIComponent(val)
+    },
+    urlEncodeObject (obj, ingoreFields) {
+      if (toString.call(obj) != '[object Object]') {
+        return obj
+      }
+      var result = {}
+      for (var key in obj) {
+        if (this.isBlank(obj[key])) {
+          continue
+        }
+        if (ingoreFields != null && ingoreFields.indexOf(key) >= 0) {
+          result[key] = obj[key]
+        } else {
+          result[key] = this.urlEncode(obj[key])
+        }
+      }
+      return result
+    },
+
+    // 根据数据字典,查询指定字典dict指定值code的,返回整个dictItem{id, text, extend}
+    getDictItemByCode (dict, code) {
+      var dicts = JSON.parse(localStorage.getItem('gaeaDict'))
+      if (!dicts.hasOwnProperty(dict)) {
+        return null
+      }
+      var dictItems = dicts[dict]
+      for (var i = 0; i < dictItems.length; i++) {
+        var dictItem = dictItems[i]
+        if (typeof (code) == 'number') {
+          code = code.toString()
+        }
+        if (dictItem['id'].toString() == code) {
+          return dictItem
+        }
+      }
+      return null
+    },
+    // 根据数据字典,查询指定字典dict指定值code的dictItem.text
+    getDictLabelByCode (dict, code) {
+      var dictItem = this.getDictItemByCode(dict, code)
+      if (dictItem != null) {
+        return dictItem['text']
+      } else {
+        return ''
+      }
+    },
+    // 根据数据字典,查询指定字典dict指定值code的dictItem.extend
+    getDictExtendByCode (dict, code) {
+      var dictItem = this.getDictItemByCode(dict, code)
+      if (dictItem == null) {
+        return null
+      }
+      var extend = dictItem['extend']
+      if (extend == null || extend.trim() == 'null') {
+        return null
+      }
+      return dictItem['extend']
+    },
   }
 }