ArcoDesin+Vue3快速构建页面组件

根据日常开发,封装表单组件,以及table组件快速构建列表及编辑页面

组件:bl-form-item

<template>
  <a-row :gutter="24">
    <a-col :span="24 / colNum" v-for="(item, index) in rowlist" :key="index">
      <a-form-item
        v-if="item.type != 'custom' && !item.hide"
        :field="item.value"
        :label="item.label"
        :label-col-flex="isInline ? '' : labelWidth"
        :rules="item.rules || null"
      >
        <!--  输入框 -->
        <template v-if="item.type == 'input'">
          <a-input :max-length="20" v-model="filterForm[item.value]" placeholder="请输入" allow-clear />
          <span class="normal" v-if="item.units">&nbsp;{
   
   { item.units }}</span>
        </template>
        <!--  下拉选择 -->
        <a-select v-if="item.type == 'select'" v-model="filterForm[item.value]" placeholder="请选择" allow-clear>
          <a-option
            v-for="option in Array.isArray(item.options) ? item.options : transformOption(item.options)"
            :value="item.optionAttr ? option[item.optionAttr.value] : option.value"
            :key="item.optionAttr ? option[item.optionAttr.value] : option.value"
            >{
   
   { item.optionAttr ? option[item.optionAttr.label] : option.label }}</a-option
          >
        </a-select>
        <!-- 单选框 -->
        <a-space size="large" v-if="item.type == 'radio'">
          <a-radio-group v-model="filterForm[item.value]">
            <a-radio
              v-for="option in Array.isArray(item.options) ? item.options : transformOption(item.options)"
              :value="item.optionAttr ? option[item.optionAttr.value] : option.value"
              :key="item.optionAttr ? option[item.optionAttr.value] : option.value"
            >
              {
   
   { item.optionAttr ? option[item.optionAttr.label] : option.label }}
            </a-radio>
          </a-radio-group>
        </a-space>
        <!--  级联选择 -->
        <a-cascader
          v-if="item.type == 'cascader'"
          v-model="filterForm[item.value]"
          placeholder="请选择"
          allow-clear
          :field-names="item.fieldNames || null"
          :multiple="item.multiple || false"
          :options="Array.isArray(item.options) ? item.options : transformOption(item.options)"
        />

        <!-- 树选择 -->
        <a-tree-select
          v-if="item.type == 'treeSelect'"
          v-model="filterForm[item.value]"
          placeholder="请选择"
          :multiple="item.multiple || false"
          :tree-checkable="item.treeCheckable || false"
          :fieldNames="item.fieldNames || null"
          :data="Array.isArray(item.options) ? item.options : transformOption(item.options)"
        >
        </a-tree-select>
        <!--  数字输入框 -->
        <!-- :formatter="formatter" :parser="parser" -->
        <template v-if="item.type == 'numberInput'">
          <a-input-number
            max-length="13"
            v-model="filterForm[item.value]"
            :placeholder="$t('numDigital.pleaseEnter')"
            :min="item.min"
            :max="item.max"
            :precision="getPrecision(item.precision)"
          />
          <span class="normal" v-if="item.units">&nbsp;{
   
   { item.units }} </span>
        </template>
        <!-- 多行输入框 -->
        <a-textarea v-if="item.type == 'textArea'" class="textarea" placeholder="请输入" v-model="filterForm[item.value]" max-length="300" />

        <!-- 日期选择 -->
        <a-date-picker style="width: 100%" value-format="YYYY-MM-DD " v-if="item.type == 'datePicker'" v-model="filterForm[item.value]" />
        <!--  日期范围 -->
        <!-- :value-format="item.valueFormat?item.valueFormat:'YYYY-MM-DD'" -->
        <a-range-picker v-if="item.type == 'rangePicker'" @change="timeChange(item)" v-model="filterForm[item.value]" />
        <!-- 月份选择 -->
        <a-month-picker style="width: 100%" v-if="item.type == 'monthPicker'" v-model="filterForm[item.value]" />
        <!--   月份范围 -->
        <a-range-picker mode="month" v-if="item.type == 'monthRangePicker'" v-model="filterForm[item.value]" />
        <!-- 输入范围 -->
        <template v-if="item.type == 'inputRange'">
          <a-input max-length="20" v-model="filterForm[item.value[0]]" placeholder="请输入" />
          <span class="normal">&nbsp;至&nbsp;</span>
          <a-input max-length="20" v-model="filterForm[item.value[1]]" placeholder="请输入" />
          <span class="normal" v-if="item.units">&nbsp;{
   
   { item.units }}</span>
        </template>
        <!--  输入数字范围 -->
        <template v-if="item.type == 'numberInputRange'">
          <!-- :formatter="formatter" :parser="parser" -->
          <a-input-number
            max-length="13"
            v-model="filterForm[item.value[0]]"
            placeholder="请输入"
            :precision="getPrecision(item.precision)"
            :min="item.min"
            :max="item.max"
          />
          <span class="normal">&nbsp;至 &nbsp;</span>
          <a-input-number
            max-length="13"
            v-model="filterForm[item.value[1]]"
            placeholder="请输入"
            :precision="getPrecision(item.precision)"
            :min="filterForm[item.value[0]]"
            :max="item.max"
          />
          <span class="normal" v-if="item.units">&nbsp;{
   
   { item.units }}</span>
        </template>
        <span v-if="item.tips" class="tips">{
   
   { item.tips }}</span>
      </a-form-item>
      
      <slot v-if="item.custom" :name="'item-' + item.custom" v-bind="{ item }" />
    </a-col>
  </a-row>
</template>
<script lang="ts" setup>
  import { ref, computed, onMounted, watch, onBeforeMount, nextTick, reactive, getCurrentInstance } from 'vue'

  import useLoading from '@/hooks/loading'
  const { loading, setLoading } = useLoading()
  import { valid } from 'mockjs'
  const emit = defineEmits(['uploadOnsuccess'])

  const props = defineProps({
    isInline: {
      type: Boolean,
      default: true,
    },
    labelWidth: {
      type: String,
      default: '90px',
    },
    type: {
      type: String,
      default: 'search',
    },
    rowlist: Array,
    /**
    * @example
    rowlist:[
    { type: 'input', label: '宠物编号/名称', value: 'keyword' },
    { type: 'cascader', label: '宠物类别/品种',value: 'sort',options: deptOptions,fieldNames: {value: 'id', label: 'deptName'}},
    { type: 'treeSelect',label: '部门',value: 'dept', options: deptOptions, fieldNames:{key: 'id',title: 'deptName',children: 'children'}},
    { type: 'select', label: '状态', value: 'status', options: [{label:'正常',value:'1'},{label:'停用',value:'2'}] },
    { type: 'select', label: '状态2', value: 'status2', optionAttr:{'label':'name','value':'code'},options: [{name:'正常',code:'1'},{name:'停用',code:'2'}] },
    { type: 'select', label: '状态3', value: 'status3', options: 'stateOptions' },
     { type: 'rangePicker', label: '时间', value: 'reportTime',startTimeField:'startTime',endTimeField:'endTime'},
  ] */
    filterForm: Object,
    colNum: {
      type: Number,
      default: 3,
    },
  })
  onMounted(() => {})

  const timeChange = (item: any) => {
    if (props.filterForm[item.value] && props.filterForm[item.value].length > 0) {
      props.filterForm[item.startTimeField || 'startTime'] = props.filterForm[item.value][0] + ' 00:00:00'
      props.filterForm[item.endTimeField || 'endTime'] = props.filterForm[item.value][1] + ' 23:59:59'
    } else {
      props.filterForm[item.startTimeField || 'startTime'] = ''
      props.filterForm[item.endTimeField || 'endTime'] = ''
    }
  }
  const transformOption = (name) => {
    return []
  }
  //对下拉框选项处理
  let optionsData = reactive({
    optionsObj: {},
  })

  /*   watch(
    () => props.filterForm,
    (val) => {
      transFormToNumber(props.list);
    },
    { deep: true }
  ); */
  //转换成数字
  const transFormToNumber = (rowList: any) => {
    for (let i = 0; i < rowList.length; i++) {
      for (let j = 0; j < rowList[i].length; j++) {
        const itemType = rowList[i][j].type
        const itemValue = rowList[i][j].value
        if (itemType == 'numberInput' && props.filterForm[itemValue]) {
          const _val = props.filterForm[itemValue]
          props.filterForm[itemValue] = Number(_val)
        }
        if (itemType == 'numberInputRange' && props.filterForm[itemValue[0]] && props.filterForm[itemValue[1]]) {
          const _val1 = props.filterForm[itemValue[0]]
          const _val2 = props.filterForm[itemValue[1]]
          props.filterForm[itemValue[0]] = Number(_val1)
          props.filterForm[itemValue[1]] = Number(_val2)
        }
      }
    }
  }
  const formatter = (value: any) => {
    const values = value.split('.')
    values[0] = values[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')

    return values.join('.')
  }

  const parser = (value: any) => {
    const val = value.replace(/,/g, '')
    return val
  }
  //数字输入精度
  const getPrecision = computed(() => {
    return (precisionVal: Number) => {
      if (precisionVal) {
        return precisionVal
      } else if (precisionVal == 0) {
        return precisionVal
      } else {
        return 2
      }
    }
  })
  const toNumber = computed(() => {
    return (val: any) => {
      return Number(val)
    }
  })
  //表单校验规则
  const getFormRules = computed(() => {
    return (rule: any, label: any, type: any) => {
      let typeMessage = ''
      if (type == 'select' || type == 'radio' || type == 'datePicker' || type == 'rangePicker') {
        typeMessage = '请选择'
      } else {
        typeMessage = '请输入'
      }
      if (rule && rule.required) {
        let ruleArr: any = [{ required: true, message: typeMessage + label }]
        return ruleArr
      } else {
        return []
      }
    }
    //
  })
</script>
<style lang="less" scoped>
  .normal {
    flex: none;
    color: var(--color-text-2);
    font-size: 14px;
  }

  .textarea {
    /*   width: 464px;
      height: 120px; */
  }
  .tips {
    /* position: absolute;
   
    z-index: 99;
    display: inline-block;
    width: 500px; */
    position: absolute;
    display: block;
    left: calc(100% + 5px);
    z-index: 99;
    width: 366px;
    line-height: 32px;
    text-align: left;
    font-size: 12px;
    color: var(--color-neutral-6);
  }
  :deep {
    .arco-form-item-label-col > .arco-form-item-label {
      overflow: hidden;
      white-space: nowrap;
    }

    .arco-radio-group .arco-radio {
      margin-right: 0;
    }
    .arco-row {
      position: relative;
    }
  }
</style>

bl-filter-form组件

<template>
  <div class="filter-container">
    <div class="p-tit">查询</div>
    <a-row class="grid-demo">
      <a-col :span="20">
        <bl-form-item v-bind="$attrs" :filterForm="filterForm"></bl-form-item>
      </a-col>
      <a-col :span="4" class="actions">
        <a-space size="small">
          <a-button type="primary" @click="handleSearch">
            <template #icon>
              <icon-search />
            </template>
            <template #default>查 询</template>
          </a-button>
          <a-button @click="handleReset">
            <template #icon>
              <icon-sync />
            </template>
            <template #default>重 置</template>
          </a-button>
        </a-space>
      </a-col>
    </a-row>
  </div>
</template>
<script lang="ts" setup>
  import blFormItem from '@/components/blade/bl-form-item.vue';
  import {
    defineEmits,
    ref,
    computed,
    onMounted,
    watch,
    onBeforeMount,
    defineExpose,
    nextTick,
    reactive,
    getCurrentInstance,
  } from 'vue';

  const props = defineProps({
    filterForm: Object,
  });
  const emit = defineEmits(['getList']);
  /******查询*****/
  onMounted(() => {});
  //查询
  const handleSearch = () => {
    emit('getList', {
      filterForm: props.filterForm,
      page: {
        pageSize: 10,
        currPage: 1,
      },
    });
  };
  //重置
  const handleReset = () => {
    const len = Object.keys(props.filterForm).length || 0;
    if (len > 0) {
      for (let key in props.filterForm) {
        props.filterForm[key] = '';
      }
    }
    emit('getList', {
      filterForm: props.filterForm,
      page: {
        pageSize: 10,
        currPage: 1,
      },
    });
  };
</script>
<style lang="less" scoped>
  .filter-container {
    margin-bottom: 24px;
    background: var(--color-bg-2);
    border-bottom: 1px solid #f7f8fa;

    .p-tit {
      height: 40px;
      margin-bottom: 10px;
      color: var(--color-text-1);
      font-size: 18px;
      line-height: 40px;
    }

    .actions {
      padding-left: 10px;
      text-align: right;
    }
  }
</style>

bl-table组件:

<template>
  <div class="table-wrapper">
    <div id="scrollContent">
      <a-table
        ref="MyTable"
        :columns="tableColumns"
        :data="tabledata"
        :scroll="{
          x: tableColumns.length * 130 || 1200,
          y: customHeight ? customHeight : tableScrollY || 200,
        }"
        :style="{
          height: (customHeight ? customHeight : tableScrollY || 200) + 'px',
        }"
        :row-key="recordIdAttr"
        :pagination="false"
        @selection-change="handleSelectionChange"
        @sorter-change="handleSorterChange"
        @expanded-change="handleExpandedChange"
        :row-selection="checkbox ? 'rowSelection' : undefined"
        :loading="loading"
        :defaultExpandAllRows="isExpandAll"
      >
        <!-- <a-table-column type="selection" width="55">
        </a-table-column> -->
        <template #columns>
          <a-table-column title="序号" width="70" v-if="showIndex">
            <template #cell="{ column, record, rowIndex }">
              <span>{
   
   { (pageParams.currPage - 1) * pageParams.pageSize + rowIndex + 1 }}</span>
            </template>
          </a-table-column>
          <a-table-column
            :title="col.title"
            v-for="col in tableColumns"
            :key="col.id"
            :data-index="col.custom ? '' : col.dataIndex"
            :sortable="col.sortable || ''"
            :ellipsis="col.ellipsis || false"
            :fixed="col.fixed || ''"
            :width="col.width || 150"
            :align="col.align || 'left'"
            :tooltip="col.tooltip || true"
          >
            <template #cell="{ column, record, rowIndex }" v-if="col.custom">
              <slot :name="'table-' + col.custom" v-bind="{ record, rowIndex, column }"></slot>
            </template>
          </a-table-column>
          <a-table-column title="操作" :width="actionWidth" v-if="hasAction" fixed="right" align="center">
            <template #cell="{ column, record, rowIndex }">
              <slot name="table-action" v-bind="{ record, rowIndex, column }"></slot>
            </template>
          </a-table-column>
        </template>
      </a-table>
    </div>
    <div id="page" class="page" v-show="!cannotPagination">
      <a-pagination
        :total="pageParams.total"
        :current="pageParams.currPage"
        :page-size="pageParams.pageSize"
        show-page-size
        show-jumper
        show-total
        @change="handleChange"
        @page-size-change="handlePageSizeChange"
      />
    </div>
  </div>
</template>
<script lang="ts" setup>
  import { ref, computed, onMounted, onUnmounted, watch, onBeforeMount, nextTick, reactive, getCurrentInstance } from 'vue'
  import { Table } from '@arco-design/web-vue'
  import { ArgumentsType } from '@vueuse/core'

  const props = defineProps({
    /**
     * @params 是否显示序号 默认显示
     */
    showIndex: {
      type: Boolean,
      default: true,
    },
    /**
     * @params 是record 的唯一标志名称,默认为id,用于传递到详情页面
     */
    recordIdAttr: {
      type: String,
      default: 'id',
    },
    /**
     * @params 自定义table滚动区域高度
     */
    customHeight: {
      type: Number,
    },
    /**
     * @params 列表中每列对应字段
     */
    tableColumns: {
      type: Array,
      default: function () {
        return []
      },
    },

    /**
 * @example
 * tableColumns: [
 { title: '宠物编号', dataIndex: 'Num', fixed: 'left', width:180 },
 { title: '宠物名称', dataIndex: 'name',ellipsis:true },
 { title: '宠物名类别/名称', dataIndex: 'sort', custom: 'sort' },
 { title: '宠物性别', dataIndex: 'sex', custom: 'sex' ,align:'center'},
 { title: '宠物年龄', dataIndex: 'age' ,align:'center'},
 {
  title: '检测报告数',
  dataIndex: 'reportNum',
  sortable: {
    sortDirections: ['ascend'],
  },
  align:'center'
},
 {
  title: 'AI问诊数',
  dataIndex: 'AINum',
  sortable: {
    sortDirections: ['ascend'],
  },
  align:'center'
},
align:'center'
},
{
title: '线上问诊数',
dataIndex: 'onlineNum',
sortable: {
  sortDirections: ['ascend'],
},
 { title: '状态', dataIndex: 'status', custom: 'status' },
 ],
 */
    /**
     * @params 列表数据
     */
    tabledata: {
      type: Array,
      default: function () {
        return []
      },
    },

    /**
     * @params 是否有操作栏 默认显示操作列
     */
    hasAction: {
      type: Boolean,
      default: true,
    },
    /**
     * @params 是否有复选框 默认显示操作列
     */
    checkbox: {
      type: Boolean,
      default: true,
    },
    /**
     * @params 复选框选择的数据列ID
     */
    rowKeys: {
      type: Array,
      default: function () {
        return []
      },
    },
    /**
     * @params 操作栏 宽度 默认显200
     */
    actionWidth: {
      type: Number,
      default: 200,
    },
    /**
     * @params 分页参数
     */
    pageParams: {
      type: Object,
      default: function () {
        return {
          total: 0,
          currPage: 1,
          pageSize: 10,
        }
      },
    },
    /**
     * @params 加载状态
     */
    loading: {
      type: Boolean,
      default() {
        return false
      },
    },
    /**
     * @params 展开状态
     */
    isExpandAll: {
      type: Boolean,
      default() {
        return true
      },
    },
    /**
     * 是否需要分页
     */
    cannotPagination: {
      type: Boolean,
      default() {
        return false
      },
    },
  })

  // 调用组件实例
  const MyTable = ref()
  const tableMethods = {}
  Object.keys(Table.methods).forEach((i) => {
    // debugger
    tableMethods[i] = (...args) => MyTable.value[i].apply(this, args)
  })
  defineExpose({
    ...tableMethods,
  })
  const emit = defineEmits(['pageChange', 'selectionChange', 'sorterChange', 'expandChange'])
  /******tableScrollY*****/
  const tableScrollY = ref(1000)
  onMounted(() => {
    calcTableScrollY()
    //监听屏幕尺寸变化调整滚动宽高值
    window.addEventListener('resize', calcTableScrollY)
  })
  onUnmounted(() => {
    window.removeEventListener('resize', calcTableScrollY)
  })
  const calcTableScrollY = () => {
    tableScrollY.value = calculateHeight()
  }
  /**********************分页********************** */
  //页码改变
  const handleChange = (value) => {
    props.pageParams.currPage = value
    emit('pageChange', props.pageParams)
  }
  //数据条数改变
  const handlePageSizeChange = (value) => {
    props.pageParams.pageSize = value
    props.pageParams.currPage = 1 // 数据条数改变,页码置为1;
    emit('pageChange', props.pageParams)
  }
  const handleSelectionChange = (rowKeys: (string | number)[]) => {
    console.log('勾选的表格数据', rowKeys)
    emit('selectionChange', rowKeys)
  }
  /**
   * 排序规则发生改变时触发
   */
  const handleSorterChange = (dataIndex: string, direction: string) => {
    emit('sorterChange', dataIndex, direction)
  }
  /**
   * 已展开的数据行发生改变时触发
   */
  const handleExpandedChange = (rowKeys: (string | number)[]) => {
    emit('expandChange', rowKeys)
  }
  /******************* 计算表单高度 *********************/
  //计算table实际高度
  const calculateHeight = (_cutClassNames = [], cutHeight = 0) => {
    let otherHeight = 0 //caculateOtherHeight(cutClassNames);
    const windowHeight = document.body.clientHeight || document.documentElement.clientHeight || window.innerHeight // 窗口高度
    const top = document.getElementById('scrollContent') ? document.getElementById('scrollContent').getBoundingClientRect().top : 0 //滚动区域到顶部距离
    const pageHeight = document.getElementById('page').offsetHeight //分页高度
    const footerHeight = document.getElementById('footer').offsetHeight //footer公司信息高度
    const pageContent = document.getElementById('pageContent')
    let bottomPx = window.getComputedStyle(pageContent).paddingBottom || 0
    let bottomPxM = window.getComputedStyle(pageContent).marginBottom || 0
    const bottom = Number(bottomPx.replace('px', '') || 0)
    const bottomM = Number(bottomPxM.replace('px', '') || 0)
    const total = top + bottom + bottomM + otherHeight + pageHeight + footerHeight
    const height = windowHeight - total - cutHeight
    return height
  }
  // 计算需要减去的div高度
  const caculateOtherHeight = (cutClassNames = []) => {
    let total = 0
    if (cutClassNames && cutClassNames.length > 0) {
      cutClassNames.forEach((className) => {
        const h = document.getElementsByClassName(className)[0].offsetHeight || 0
        total = total + h
      })
    }
    return total
  }
</script>
<style lang="less" scoped>
  .page {
    box-sizing: border-box;
    height: 50px;
    line-height: 50px;
    text-align: center;
  }
</style>

文件上传:

<template>
  <div ref="downloadDom">
    <a-upload
      v-if="type == 'add' || type == 'edit'"
      :action="uploadUrl"
      :headers="headers"
      @success="uploadSuccess"
      :on-before-remove="beforeRemove"
      name="files"
      :image-preview="imagePreview"
      @before-upload="beforeUpload"
      :limit="limit"
      :accept="accept"
      :file-list="reactiveData.fileList"
      :default-file-list="reactiveData.fileList"
      :show-upload-button="showUploadBtn"
      :show-remove-button="showRemoveBtn"
      :download="true"
      :showFileList="showFileList"
      :fileSize="fileSize"
      :list-type="listType"
    >
      <template #upload-button v-if="custom">
        <slot name="button"></slot>
      </template>
    </a-upload>
  
    <div v-else>
      <template v-if="reactiveData.fileList && reactiveData.fileList.length > 0">
        <a-link
          style="display: block"
          v-for="(file, index) in reactiveData.fileList"
          :download="file.name.substring(file.name.lastIndexOf('/') + 1)"
          :key="index"
          :href="file.url"
          :hoverable="false"
        >
          <a-space>
            <icon-file />
            {
   
   { file.name.substring(file.name.lastIndexOf('/') + 1) }}
          </a-space>
        </a-link>
      </template>

      <template v-else> 暂无 </template>
    </div>
  </div>
</template>

<script lang="ts" setup>
  import { ref, reactive, computed, defineEmits, defineProps, defineExpose, watch, toRaw, getCurrentInstance, onMounted, nextTick } from 'vue'
  import { Message, Modal } from '@arco-design/web-vue'
  import useLoading from '@/hooks/loading'
  const { loading, setLoading } = useLoading()
  import { getToken } from '@/utils/auth'
  const emit = defineEmits(['onsuccess', 'beforeUpload'])
 
  const uploadUrl = ref("");
  uploadUrl.value=import.meta.env.VITE_DOWNLOAD_HOST+'/file/upload';
  const props = defineProps({
    /**
     * @params 文件列表数据
     */
    fileList: {
      type: Array,

      default: [],
    },
    /**
     * @params 接收的上传文件类型
     */
    accept: {
      type: String,
      default: '.png,.jpg,.jpeg',
    },
    /**
     * @params 上传文件个数
     */
    limit: {
      type: Number,
      default: 1,
    },
    /**
     * @params 图片预览功能是否展示
     */
    imagePreview: {
      type: Boolean,
      default: true,
    },
    /**
     * @params 上传图标是否展示
     */
    showUploadBtn: {
      type: Boolean,

      default: true,
    },
    /**
     * @params 删除图标是否展示
     */
    showRemoveBtn: {
      type: Boolean,

      default: true,
    },
    /**
     * @params edit add新增编辑可以上传  view只是查看
     */
    type: {
      type: String,
      default: 'add',
    },
    /**
     * @params 上传图片大小最大多少M
     */
    fileSize: {
      type: Number,

      default: 5,
    },
    /**
     * @params 图片列表类型
     */
    listType: {
      type: String,
      default: 'picture-card',
    },
    /**
     * @params 自定义上传图标的插槽名
     */
    slotName: String,
    custom: {
      type: Boolean,
      default: false,
    },
    /**
     * @params 是否显示文件列表
     */
    showFileList: {
      type: Boolean,

      default: true,
    },
  })
  const reactiveData = reactive({
    fileList: [],
  })
  //监听
  watch(
    () => props.fileList,
    (val) => {
      reactiveData.fileList = props.fileList

      nextTick(() => {
        addClickEvent()
      })
    },

    { deep: true, immediate: true }
  )

  /*******upload Header********/

  const headers = computed(() => {
    const token = getToken()
    return {
      client: '1', // 客户端类型 1 web端 2 移动端
      Authorization: `${token}`,
    }
  })

  /* 删除 */
  const beforeRemove = (file) => {
    let index = reactiveData.fileList.findIndex((item) => item.id === file.id)

    reactiveData.fileList.splice(index, 1)

    emit('onsuccess', reactiveData.fileList)
  }
  /* 上传前文件大小限制提醒 */
  const beforeUpload = (file) => {
    const isLtM = file.size / 1024 / 1024 < props.fileSize

    if (!isLtM) {
      Message.error('文件不能超过M' + props.fileSize + 'M')
    }
    emit('beforeUpload')

    return isLtM
  }

  /* 上传成功 */

  const uploadSuccess = (file) => {
    const res = file.response
    if (res?.data) {
      let data = res.data
      let imgPath = data[0] ? data[0].filePath : ''
      let fileId = data[0] ? data[0].fileId : ''

      const obj = {
        name: file.name,
        id: fileId,
        url: 'http://10.60.0.44:8950' + imgPath,
        imgPath: imgPath,
      }
      reactiveData.fileList.push(obj)
      let filesStr = ''
      if (reactiveData.fileList && reactiveData.fileList.length) {
        const imgpathArr = reactiveData.fileList.map((item) => {
          return item.imgPath
        })
        filesStr=imgpathArr.join(',')
        console.log(filesStr);
      }
      emit('onsuccess', reactiveData.fileList,filesStr)
    }
  }

  const { proxy } = getCurrentInstance()
  const addClickEvent = () => {
    if (!proxy.$refs['downloadDom']) {
      return
    }
    let downloadDom = proxy.$refs['downloadDom'].getElementsByTagName('a')
    let links = Object.values(downloadDom)
    links.forEach((link) => {
      link.onclick = (e) => {
        e.preventDefault()
        downloadFile(link.href + '?secret=' + keyId.value, link.download)
      }
    })
  }
  //文件下载
  const downloadFile = (url: any, fileName: any) => {
    fetch(url).then((res) => {
      res.blob().then((blob) => {
        const blobUrl = window.URL.createObjectURL(blob)

        // 这里的文件名根据实际情况从响应头或者url里获取

        const filename = fileName

        const a = document.createElement('a')

        a.href = blobUrl

        a.download = filename

        a.click()

        window.URL.revokeObjectURL(blobUrl)
      })
    })
  }
</script>
<style lang="less" scoped></style>

列表页面:

<template>
  <div class="page_right">
    <!-- 筛选列表 -->
    <bl-filter-form :isInline="false" :rowlist="rowlist" :filterForm="filterForm" @getList="getList"></bl-filter-form>
    <div class="table-box">
      <!-- 列表数据 -->
      <bl-table
        :pageParams="table.page"
        :tableColumns="table.tableColumns"
        :tabledata="table.tabledata"
        @pageChange="handlePageChange"
        :showIndex="false"
      >
        <template #table-sex="{ record }">
          {
   
   { record.petGender == 1 ? '公' : '母' }}
        </template>
        <template #table-age="{ record }"> {
   
   { record.ageYear }}{
   
   { record.ageMonth }} </template>
        <template #table-status="{ record }">
          <template v-if="record.status"> <span class="prohibition"></span>禁用</template>
          <template v-else> <span class="normal"></span>正常 </template>
        </template>
        <template #table-sort="{ record }"> {
   
   { record.animalType }}/{
   
   { record.petType }} </template>
        <template #table-action="{ record }">
          <a-space size="mini">
            <a-button type="text" @click="gotoPage('petManagedetail', { id: record.id })">详 情</a-button>
            <a-button type="text" status="danger" @click="handleDelete(record.id)">删 除</a-button>
          </a-space>
        </template>
      </bl-table>
    </div>
  </div>
</template>
<script lang="ts" setup>
  import { ref, reactive, watch, computed, onMounted, onBeforeUnmount, getCurrentInstance, onUnmounted, onBeforeMount } from 'vue'
  /* import { Message, Modal } from '@arco-design/web-vue'; */
  import blFilterForm from '@/components/blade/bl-filter-form.vue'
  import blTable from '@/components/blade/bl-table.vue'
  import { petsPage, petsCatalogueTree } from '@/api/petManagement/pet'
  /*   import { loginLogDept } from '@/api/systemManage/logManagement'; */
  import { useRoute, useRouter } from 'vue-router'
  const route = useRoute()
  const router = useRouter()

  /*  筛选 */
  const filterForm = reactive({
    petsKeyWord: '',
    petType: '',
    status: '',
  })
  /* 请求宠物类别品种树数据 */
  const petsCatalogOptions = ref([])
  const getPetsCatalogueTreeData = async () => {
    let res = await petsCatalogueTree()
    petsCatalogOptions.value = res.data || []
    console.log(res)
  }
  const rowlist = reactive([
    { type: 'input', label: '宠物编号/名称', value: 'petsKeyWord' },
    {
      type: 'cascader',
      label: '宠物类别/品种',
      value: 'petType',
      options: petsCatalogOptions,
      fieldNames: { value: 'id', label: 'name' },
    },
    /*   { type: 'treeSelect',label: '部门',value: 'deptID', options: deptOptions, fieldNames:{key: 'id',title: 'deptName',children: 'children'}}, */
    {
      type: 'select',
      label: '状态',
      value: 'status',
      options: [
        { label: '正常', value: '1' },
        { label: '停用', value: '2' },
      ],
    },
  ])

  /* table列表 */
  const table = reactive({
    tabledata: [],
    page: { total: 0, pageSize: 10, currPage: 1 },
    tableColumns: [
      { title: '宠物编号', dataIndex: 'id', fixed: 'left', width: 190 },
      {
        title: '宠物名称',
        dataIndex: 'petName',
        ellipsis: true,
        tooltip: true,
        width: 190,
      },
      { title: '宠物名类别/品种', dataIndex: 'sort', custom: 'sort' },
      {
        title: '宠物性别',
        dataIndex: 'petGender',
        custom: 'sex',
        align: 'center',
      },
      { title: '宠物年龄', dataIndex: 'age', align: 'center', custon: 'age' },
      {
        title: '检测报告数',
        dataIndex: 'medicalReportNum',
        sortable: {
          sortDirections: ['ascend'],
        },
        align: 'center',
      },
      {
        title: 'AI问诊数',
        dataIndex: 'aiInterrogationNum',
        sortable: {
          sortDirections: ['ascend'],
        },
        align: 'center',
      },
      {
        title: '线上问诊数',
        dataIndex: 'onlineInterrogationNum',
        sortable: {
          sortDirections: ['ascend'],
        },
        align: 'center',
      },
      { title: '状态', dataIndex: 'status', custom: 'status' },
    ],
  })
  //分页
  const handlePageChange = (pageParams) => {
    table.page = pageParams
    getList()
  }
  /*********路由跳转*********************** */
  const gotoPage = (pageName: String, query: Object) => {
    router.push({
      name: pageName,
      query: query,
    })
  }
  onBeforeMount(() => {
    /* getDeptListFun(); */
    getPetsCatalogueTreeData()
  })
  onMounted(() => {
    getList()
  })

  /******请求列表数据******/
  const getList = async (data:any=null) => {
    if (data) {
      table.page = data.page
    }
    const params = Object.assign(filterForm, table.page)
    let res = await petsPage(params)
    table.tabledata = (res.data && res.data.list) || []
    table.page = {
      total: res.data.totalCount,
      pageSize: res.data.pageSize,
      pageNum: res.data.currPage,
    }
  }
  //删除
  const handleDelete = (id) => {}
</script>

<style lang="less" scoped></style>

新增、编辑页:

<template>
  <div class="page_right page_right_detail">
    <div class="d-container">
      <a-form ref="formRef" :model="formData.form" @submit="handleSaveInfo">
        <div class="form-block" v-for="(bitem, index) in formData.formList" :key="index">
          <div class="part-tit">{
   
   { bitem.title }}</div>
          <div v-if="bitem.custom" class="b-tips">
            <div>1.可选择不开票</div>
            <div>2.若选择开票,开票主体一旦确认,无法更改</div>
            <div>3.若选择开专票,需为公对公打款客户</div>
          </div>
          <bl-form-item
            :colNum="bitem.colNum"
            labelWidth="120px"
            :filterForm="formData.form"
            :rowlist="bitem.rows"
            :isInline="false"
            :style="{ width: bitem.width || '100%', margin: '0 auto' }"
          >
            <!-- 门内照 -->
            <template #item-storePhotos="{ item }">
              <a-form-item :field="item.value" :label="item.label" :rules="item.rules">
                <uploadFiles :limit="5" @onsuccess="storePhotosUploadOnsuccess" :fileList="storePhotosList"> </uploadFiles>
              </a-form-item>
            </template>
            <!-- 门牌号 -->
            <template #item-storeNumber="{ item }">
              <a-form-item :field="item.value" :label="item.label" :rules="item.rules">
                <uploadFiles @onsuccess="storeNumberUploadOnsuccess" :fileList="storeNumberPhotoList"> </uploadFiles>
              </a-form-item>
            </template>
            <!-- 行业相关资质照片 -->
            <template #item-qualificationPhotos="{ item }">
              <a-form-item :field="item.value" :label="item.label" :rules="item.rules">
                <uploadFiles :limit="5" @onsuccess="qualificationPhotosOnsuccess" :fileList="qualificationPhotosList"> </uploadFiles>
              </a-form-item>
            </template>

            <!--  营业执照 -->
            <template #item-businessLicense="{ item }">
              <a-form-item :field="item.value" :label="item.label" :rules="item.rules">
                <uploadFiles @onsuccess="businessLicenseOnsuccess" :fileList="businessLicenseList"> </uploadFiles>
              </a-form-item>
            </template>
            <!-- 开票主体营业执照 -->
            <template #item-invoiceBusinessLicense="{ item }">
              <a-form-item :field="item.value" :label="item.label" :rules="item.rules" v-if="!item.hide">
                <uploadFiles @onsuccess="invoiceBusinessLicenseOnsuccess" :fileList="invoiceBusinessLicenseList"> </uploadFiles>
              </a-form-item>
            </template>
            <!-- 专票授权文件 -->
            <template #item-dedicateeInvoice="{ item }">
              <a-form-item :field="item.value" :label="item.label" :rules="item.rules" v-if="!item.hide">
                <uploadFiles @onsuccess="dedicateeInvoiceeOnsuccess" :fileList="dedicateeInvoiceList"> </uploadFiles>
              </a-form-item>
            </template>
          </bl-form-item>
        </div>
       <!--  <div>添加产品线</div> -->
        <div class="btn-actions">
          <a-space size="large">
            <a-button type="primary" html-type="submit">保 存</a-button>
            <a-button @click="handleReset">重 置</a-button>
          </a-space>
        </div>
      </a-form>
    </div>
  </div>
</template>
<script setup lang="ts">
  import { ref, reactive, watch, computed, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'
  import blFormItem from '@/components/blade/bl-form-item.vue'
  import useLoading from '@/hooks/loading'
  import { addStore, updateStore, getStoreInfo } from '@/api/customManagement/store'
  import uploadFiles from '@/components/uploadFile/index.vue'
  import { Message, Modal } from '@arco-design/web-vue'
  import { useRoute, useRouter } from 'vue-router'
  const route = useRoute()
  const router = useRouter()
  const { loading, setLoading } = useLoading()
  onMounted(() => {
    if (route.query.id) {
      getIndoDetail()
    }
  })

  const licencesBol = ref(false)
  const workTimeBol = ref(false)
  const invoiceBol = ref(true)
  const dedicateeBol = ref(false)
  const formData = reactive({
    form: {
      bindingPhone: '',
      companyAddress: '',
      contact: '',
      hotline: '',
      openingHours: '1',
      qualificationPhotos: [],
      storeAddress: '江西省赣州市章贡区沙河工业园区',
      storeLatitude: '25.830455',
      storeLongitude: '114.973604',
      storeName: '',
      timeFrameList: [],
      storePhotos: [],
      storeNumberPhoto: '',
      businessLicense: '',
      businessLicenseName: '',
      businessLicenseCode: '',
      identifyType: '1',
      businessModel: 1,
      invoiceType: 1,
      isInvoice: '1',
      invoiceLatitude: '25.830455',
      invoiceLongitude: '114.973604',
      invoiceBusinessLicense: '',
      invoiceBusinessLicenseName: '',
      invoiceBusinessLicenseCode: '',
      courtSummons:"",
      invoiceOrganizationCode:"",
      invoiceTaxEnrolCertificate:"",
    },
    formList: [
      {
        colNum: 1,
        width: '700px',
        title: '基本信息',
        rows: [
          {
            type: 'input',
            label: '门店名称',
            value: 'storeName',
            tips: '门店名称会显示在小程序前端,请谨慎填写',
            rules: [{ required: true, message: '请输入门店名称' }],
          },
          {
            type: 'input',
            label: '绑定手机号',
            value: 'bindingPhone',
            tips: '该手机号码可用于忘记密码进行短信校验,请确保填写号码的真实性',
            rules: [{ required: true, message: '请输入账号绑定手机号' }],
          },
          {
            type: 'input',
            label: '联系人',
            value: 'contact',
            rules: [{ required: true, message: '请输入联系人' }],
          },
          {
            type: 'select',
            label: '门店地址',
            value: 'storeAddress',
            options: [],
            rules: [{ required: true, message: '请选择门店地址' }],
          },
          {
            type: 'input',
            label: '热线电话',
            value: 'hotline',
            rules: [{ required: true, message: '请输入热线电话' }],
          },

          {
            type: 'radio',
            label: '营业时间',
            value: 'openingHours',
            options: [
              { label: '24小时全天开放', value: '1' },
              { label: '选择时间段', value: '2' },
            ],
            rules: [{ required: true, message: '请选择营业时间' }],
          },
          {
            type: 'rangePicker',
            label: ' ',
            hide: workTimeBol,
            value: 'timeFrameList',
            startTimeField: 'startTime',
            endTimeField: 'endTime',
          },
          {
            type: 'custom',
            label: '店内照片',
            value: 'storePhotos',
            custom: 'storePhotos',
            rules: [{ required: true, message: '请上传店内照片' }],
          },
          {
            type: 'custom',
            label: '门牌号',
            value: 'storeNumberPhoto',
            custom: 'storeNumber',
            rules: [{ required: true, message: '请上传吗门牌号照片' }],
          },
        ],
      },
      {
        colNum: 1,
        width: '700px',
        title: '经营信息',
        rows: [
          {
            type: 'select',
            options: [{ label: '经营模式1', value: 1 }],
            label: '经营模式',
            value: 'businessModel',
            rules: [{ required: true, message: '请选择经营模式' }],
          },
          {
            type: 'custom',
            label: '行业相关资质照片',
            value: 'qualificationPhotos',
            custom: 'qualificationPhotos',
            rules: [{ required: true, message: '请上传行业相关资质照片' }],
          },
          {
            type: 'custom',
            label: '营业执照',
            value: 'businessLicense',
            custom: 'businessLicense',
            rules: [{ required: true, message: '请上传营业执照' }],
          },
          {
            type: 'input',
            label: '营业执照名称',
            value: 'businessLicenseName',
            rules: [{ required: true, message: '请填写营业执照名称' }],
          },
          {
            type: 'input',
            label: '营业执照编码',
            value: 'businessLicenseCode',
            rules: [{ required: true, message: '请填写营业执照编码' }],
          },
          {
            type: 'radio',
            label: '认证类型',
            value: 'identifyType',
            options: [
              { label: '三证合一', value: '1' },
              { label: '三证', value: '2' },
            ],
            rules: [{ required: true, message: '请选择认证类型' }],
          },

          {
            type: 'input',
            hide: licencesBol,
            label: '组织机构代码编码',
            value: 'organizationCode',
            custom: 'organizationCode',
            rules: [{ required: true, message: '请填写组织机构代码编码' }],
          },
          {
            type: 'input',
            hide: licencesBol,
            label: '税务登记证编码',
            value: 'taxCode',
            custom: 'taxCode',
            rules: [{ required: true, message: '请填写税务登记证编码' }],
          },
        ],
      },
      {
        colNum: 1,
        width: '700px',
        custom: true, //自定义
        title: '开票信息',
        rows: [
          {
            type: 'radio',
            label: '是否开票',
            value: 'isInvoice',
            options: [
              { label: '是', value: '1' },
              { label: '否', value: '0' },
            ],
            rules: [{ required: true, message: '请选择是否开票' }],
          },
          {
            type: 'radio',
            label: '开票类型',
            value: 'invoiceType',
            hide: invoiceBol,
            options: [
              { label: '普通发票', value: 1 },
              { label: '专用发票', value: 2 },
            ],
            rules: [{ required: true, message: '请选择是否开票' }],
          },

          {
            type: 'custom',
            label: '开票主体营业执照',
            hide: invoiceBol,
            value: 'invoiceBusinessLicense',
            custom: 'invoiceBusinessLicense',
            rules: [{ required: true, message: '请上传开票主体营业执照' }],
          },
          {
            type: 'input',
            label: '营业执照名称',
            hide: invoiceBol,
            value: 'invoiceBusinessLicenseName',
            rules: [{ required: true, message: '请填写营业执照名称' }],
          },
          {
            type: 'input',
            label: '营业执照编码',
            hide: invoiceBol,
            value: 'invoiceBusinessLicenseCode',
            rules: [{ required: true, message: '请填写营业执照编码' }],
          },
          {
            type: 'custom',
            label: '专票授权文件',
            hide: dedicateeBol,
            value: 'courtSummons',
            custom: 'dedicateeInvoice',
            rules: [{ required: true, message: '请上传专票授权文件' }],
          },
          { type: 'input', label: '公司名称', hide: invoiceBol, value: 'companyName', rules: [{ required: true, message: '请填写公司名称' }] },
          { type: 'input', label: '税号', hide: invoiceBol, value: 'tariffItem', rules: [{ required: true, message: '请填写税号' }] },
          {
            type: 'input',
            label: '发票收件人',
            hide: invoiceBol,
            value: 'invoiceReceiver',
            rules: [{ required: true, message: '请填写发票收件人' }],
          },
          {
            type: 'input',
            label: '联系电话',
            hide: invoiceBol,
            value: 'invoiceReceiverPhone',
            rules: [{ required: true, message: '请填写联系电话' }],
          },
          { type: 'input', label: '所在地址', hide: invoiceBol, value: 'companyAddress', rules: [{ required: true, message: '请选择所在地址' }] },
          {
            type: 'input',
            hide: !invoiceBol,
            label: '组织机构代码编码',
            value: 'invoiceOrganizationCode',
            custom: 'organizationCode',
            rules: [{ required: true, message: '请填写组织机构代码编码' }],
          },
          {
            type: 'input',
            hide: !invoiceBol,
            label: '税务登记证编码',
            value: 'invoiceTaxEnrolCertificate',
            custom: 'taxCode',
            rules: [{ required: true, message: '请填写税务登记证编码' }],
          },
        ],
      },
     /*  {
        colNum: 1,
        width: '700px',
        title: '交易信息',
        rows: [],
      }, */
    ],
  })
  watch(
    () => formData.form,
    (val: any) => {
      /* 认证类型切换 */
      if (val.identifyType == '2') {
        licencesBol.value = false
      } else {
        licencesBol.value = true
      }
      /* 营业时间切换 */
      if (val.openingHours == '2') {
        workTimeBol.value = false
      } else {
        workTimeBol.value = true
      }
      /* 是否开票切换 */
      if (val.isInvoice == '1') {
        invoiceBol.value = false
      } else {
        invoiceBol.value = true
      }
      /* 开票类型 */
      if (val.invoiceType == 1) {
        dedicateeBol.value=true
      }else{
         dedicateeBol.value=false
      }
    },
    {
      immediate: true,
      deep: true,
    }
  )

  //返回
  const goBack = () => {
    router.go(-1)
  }
  /*********图片上传************* */
  /* 门内店照 */
  const storePhotosList = ref([])
  const storePhotosUploadOnsuccess = (files, filesStr) => {
    storePhotosList.value = files
    formData.form.storePhotos = filesStr.split(',')
  }
  /* 门牌号 */
  const storeNumberPhotoList = ref([])
  const storeNumberUploadOnsuccess = (files, filesStr) => {
    storeNumberPhotoList.value = files
    formData.form.storeNumberPhoto = filesStr
  }
  /* 行业资质照片 */
  const qualificationPhotosList = ref([])
  const qualificationPhotosOnsuccess = (files, filesStr) => {
    qualificationPhotosList.value = files
    formData.form.qualificationPhotos = filesStr.split(',')
  }
  /* 营业执照 */
  const businessLicenseList = ref([])
  const businessLicenseOnsuccess = (files, filesStr) => {
    businessLicenseList.value = files
    formData.form.businessLicense = filesStr
  }
  /* 开票主体营业执照 */

  const invoiceBusinessLicenseList = ref([])
  const invoiceBusinessLicenseOnsuccess = (files, filesStr) => {
    invoiceBusinessLicenseList.value = files
    formData.form.invoiceBusinessLicense = filesStr
  }
  /* 专票授权文件 */
  const dedicateeInvoiceList = ref([])
  const dedicateeInvoiceeOnsuccess = (files, filesStr) => {
    dedicateeInvoiceList.value = files
    formData.form.courtSummons = filesStr
  }
  /******请求数据******/
  const getIndoDetail = async () => {
    let res = await getStoreInfo(route.query.id)
    console.log(res.data)
    formData.form = Object.assign(formData.form, res.data)
    console.log(formData.form)
  }
  const timeChange = (item: any) => {}
  /* 重置 */
  const formRef = ref<any>(null)
  const handleReset = () => {
    formRef.value.resetFields()
    storePhotosList.value = []
    storeNumberPhotoList.value = []
    businessLicenseList.value = []
    qualificationPhotosList.value = []
    invoiceBusinessLicenseList.value = []
  }
  /******请求数据******/
  const handleSaveInfo = async ({ values, errors }) => {
    if (!errors) {
      if (route.query.id) {
        let res = await updateStore(formData.form)
        if (res.code == 0) {
          Message.success('修改成功')
          router.go(-1)
        }
      } else {
        let res = await addStore(formData.form)
        if (res.code == 0) {
          Message.success('新增成功')
          router.go(-1)
        }
      }
    }
  }
</script>

<style lang="less" scoped>
  .part-tit {
    font-size: 18px;
    height: 2em;
    line-height: 2em;
    padding-left: 10px;
    margin: 20px 0 30px;
    border-bottom: 1px solid #e5e6eb;
  }
  .b-tips {
    color: rgb(var(--arcoblue-4));
    line-height: 1.8em;
    background: rgba(42, 182, 255, 0.06);
    width: 700px;
    margin: 0 auto 30px;
    padding: 16px;
  }
  .btn-actions {
    text-align: center;
  }
</style>

猜你喜欢

转载自blog.csdn.net/Holly31/article/details/132100019