vue3 實現表格列拖拽排序、顯示隱藏; 以及行內編輯表格自帶校驗


關注公眾號: 微信搜索 前端工具人 ; 收貨更多的干貨

原文鏈接: 自己掘金文章 https://juejin.cn/post/7067039547091058696/

一、需求

  • 所有表格需根據用戶自定義顯示列、及列的顯示順序;
  • 支持左側、右側固定列、列寬修改
  • 行內編輯表格(新增、編輯)必填項自帶校驗(類似於form表單的校驗攔截)
  • 效果圖

5e2oy-b8ft5.gif

aarzi-iecpn.gif

二、方案

2.1 拖拽插件

vue-draggable-next

2.2 初期使用的是 element-plus 實現;

缺點:

  1. 當表格字段 40+ 及以上的時候, 表格卡頓,初始顯示很慢;
  2. 表格涉及行展開操作時,也響應很慢;
  3. 版本 1.1.0-beta.18, 有點舊的版本, 因為該項目是去年中旬寫的;最近 element-plus 大改升級穩定版,不知道修復沒

列表列40+甚至有的頁面60+, 確實少見誇張, 原因是表格支持導出, 導出要詳細的

2.3 ant-design-vue 實現

  1. 使用 ant-design-vue 自帶的 table 控件實現
  2. 使用 ant-design-vue 增強的 Surely Vue 高性能組件

文章使用的 1 , 因為 ant-design-vue 自帶的 table 控件已經滿足了需求, 考慮到 Surely Vue 組件需求單獨引入就沒嘗試了

2.4 表格自帶校驗

  • 新增數據的時候是在表格內新增一行,有的列是必填項、有的是自帶校驗規則,比如只能輸入數字、中文之類的校驗
  • 編輯同上; 對 table 控件進行 二次封裝 實現

三、代碼片段

3.1 element-plus 版 自定義 table 組件

// CustomTable.vue
// 代碼有刪減
<template>
  <div class="custom-table-container">
    <div class="custom-buttons">
      <el-tooltip class="item" effect="dark" content="表格設置" placement="top-start">
        <el-button icon="el-icon-setting" circle @click="fixedVisible = true"></el-button>
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="表格排序" placement="top-start">
        <el-button icon="el-icon-sort" circle @click="showVisible = true"></el-button>
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="表格刷新" placement="top-start">
        <el-button icon="el-icon-refresh" circle @click="onRefresh"></el-button>
      </el-tooltip>
    </div>
    <div class="custom-content">
      <!-- row-key="id" :tree-props="{children: 'children', hasChildren: 'hasChildren'}" -->
      <el-table
        :data="tableData" center border align="left" style="width: 100%" @selection-change="handleSelectionChange"
        :row-key="rowId" lazy :load="loadChildren" :tree-props="{children: 'children', hasChildren: 'hasChildren'}">
        <el-table-column fixed="left" width="50" align="center" type="selection"> </el-table-column>
        <el-table-column fixed="left" width="50" align="center" label="序號" type="index"></el-table-column>
        <el-table-column fixed="left" width="50" align="center" label="展開" v-if="isChildren"></el-table-column>
        <el-table-column fixed="left" :width="oWidth" align="center" label="操作" v-if="isShowOperaRow">
          <template #default="{ row }">
            <slot name="operate" :scope="row" v-if="!row.isChildren"></slot>
          </template>
        </el-table-column>
        <!-- 左側固定列 -->
        <el-table-column
          v-for="(item, index) in leftList"
          :key="item.fieldCode + index"
          align="center"
          fixed="left"
          :width="item.fieldWidth ? item.fieldWidth : '100'"
          :prop="item.fieldCode"
          :label="item.fieldName"
          show-overflow-tooltip>
        </el-table-column>
        <!-- 中間滑動列 -->
        <el-table-column
          v-for="(item, index) in showList"
          :key="item.fieldCode + index"
          align="center"
          :min-width="item.fieldWidth ? item.fieldWidth : '120'"
          :prop="item.fieldCode"
          :label="item.fieldName"
          show-overflow-tooltip>
        </el-table-column>
        <!-- 右側固定列 -->
        <el-table-column
          v-for="(item, index) in rightList"
          :key="item.fieldCode + index"
          align="center"
          fixed="right"
          :width="item.fieldWidth ? item.fieldWidth : '100'"
          :prop="item.fieldCode"
          :label="item.fieldName"
          show-overflow-tooltip>
        </el-table-column>
      </el-table>
    </div>
    <!-- 顯示隱藏 -->
    <el-dialog title="顯示隱藏" v-model="showVisible" top="100px" width="1000px" :lock-scroll="true" :close-on-click-modal="false">
      <section class="section-drag">
        <div class="item">
          <div class="title">隱藏</div>
          <VueDraggableNext class="list-group" :list="hiddenList" group="people">
            <transition-group type="transition" name="flip-list">
              <div class="list-group-item" v-for="element in hiddenList" :key="element.fieldCode"> {{ element.fieldName }} </div>
            </transition-group>
          </VueDraggableNext>
        </div>
        <div class="item">
          <div class="title">顯示</div>
          <VueDraggableNext class="list-group" :list="showList" group="people" @change="onChangeShow">
            <transition-group type="transition" name="flip-list">
              <div class="list-group-item" v-for="element in showList" :key="element.fieldCode"> {{ element.fieldName }} </div>
            </transition-group>
          </VueDraggableNext>
        </div>
      </section>
      <section class="fotter-buttons">
        <el-button size="medium" @click="showVisible = false">取消</el-button>
        <el-button size="medium" type="primary" @click="onSaveShow">保存</el-button>
      </section>
    </el-dialog>
    <!-- 固定列數 -->
    <el-dialog title="固定列數" v-model="fixedVisible" top="100px" width="1000px" :lock-scroll="true" :close-on-click-modal="false">
      <section class="section-fixed section-drag">
        <div class="item">
          <div class="title">固定左側</div>
          <VueDraggableNext class="list-group" :list="tailFixedList" group="people">
            <transition-group type="transition" name="flip-list">
              <div class="list-group-item" v-for="element in tailFixedList" :key="element.fieldCode"> {{ element.fieldName }} </div>
            </transition-group>
          </VueDraggableNext>
        </div>
        <div class="item">
          <div class="title">中間滑動列</div>
          <VueDraggableNext class="list-group" :list="showList" group="people">
            <transition-group type="transition" name="flip-list">
              <div class="list-group-item" v-for="element in showList" :key="element.fieldCode"> {{ element.fieldName }} </div>
            </transition-group>
          </VueDraggableNext>
        </div>
        <div class="item">
          <div class="title">固定右側</div>
          <VueDraggableNext class="list-group" :list="frontFixedList" group="people" @change="onChangeFixed">
            <transition-group type="transition" name="flip-list">
              <div class="list-group-item" v-for="element in frontFixedList" :key="element.fieldCode"> {{ element.fieldName }} </div>
            </transition-group>
          </VueDraggableNext>
        </div>
      </section>
      <section class="fotter-buttons">
        <el-button size="medium" @click="fixedVisible = false">取消</el-button>
        <el-button size="medium" type="primary" @click="onSaveFixed">保存</el-button>
      </section>
    </el-dialog>
  </div>
</template>
 
<script lang="ts">
import { defineComponent, reactive, toRefs, onMounted, PropType, UnwrapRef } from 'vue'
import { IColItem, ICustomTableState } from './type'
import { VueDraggableNext } from 'vue-draggable-next'
import * as INTERFACE from '@/interface'
import { IResponseData } from '@/types'
import { ElMessage } from 'element-plus'
export default defineComponent({
  name: 'CustomTable',
  components: { VueDraggableNext },
  props: {
    oWidth: {
      type: Number,
      default: 170,
    },
    isShowOperaRow: {
      type: Boolean,
      default: true
    },
    isChildren: {
      type: Boolean,
      default: false
    },
    rowId: {
      type: [Number, String],
      default: 'id'
    },
    tableCode: {
      type: String,
      default: 'testTable',
      required: true
    },
    tableData: {
      type: Array as PropType<IColItem[]>,
      default: () => []
    }
  },
  emits: { 'onRefresh': null, 'loadChildren': null ,'handleSelectionChange': null },
  setup(props, ctx) {
    // 避免排序、顯示隱藏相互影響, 深拷貝列表字段
    const state: UnwrapRef<ICustomTableState> = reactive({
      showVisible: false,
      fixedVisible: false,
      leftList: [],
      centerList: [],
      rightList: [],
      hiddenList: [],
      showList: [],
      tailFixedList: [],
      middleSlidingList: [],
      frontFixedList: []
    })
    onMounted(() => {
      // 初始根據傳遞的 tableCode 獲取對應的列表字段
      getTableHeaders()
    })
    // ... 省略業務代碼
    return { ...toRefs(state), onSaveShow, onSaveFixed, onRefresh, loadChildren, handleSelectionChange }
  }
})
</script>

3.2 ant-design-vue 版 自定義 table 組件

注意 ant-design-vueelement-plus API 不一樣

// CustomTable.vue
// 代碼其他部分變化, 主要是改變 table 部分
...
<a-table
  size="small"
  bordered
  :rowKey="rowKey"
  :pagination="false"
  :columns="columnsData"
  :data-source="tableData"
  :scroll="{ x: 1500, y: 500 }"
  @expand="onRowExpand"
  :expandRowByClick="true"
  childrenColumnName="childrenData"
  :expandIconColumnIndex="2"
  @resizeColumn="handleResizeColumn"
  :rowClassName="(record, index) => (index % 2 === 1 ? 'table-striped' : null)"
  :row-selection="{ selectedRowKeys: selectedKeys, onChange: onSelectChange }">
  <template #expandIcon="{ record }">
    <i v-if="record.expandStatus === 3" class="el-icon-arrow-down expand-icon"></i>
    <i v-if="record.expandStatus === 2" class="el-icon-loading expand-icon"></i>
    <i v-if="record.expandStatus === 1" class="el-icon-arrow-right expand-icon"></i>
  </template>
  <template #bodyCell="{ column, record, index }">
    <template v-if="column.key === 'index' && isChildren">
      <span v-if="!record.isChildren">{{ index + 1 }}</span>
    </template>
    <template v-if="column.key === 'operate' && isShowOperaRow">
      <div v-if="!record.isChildren"><slot name="operate" :scope="record"></slot></div>
      <el-button type="text" v-else style="padding: 6px 0"></el-button>
    </template>
  </template>
</a-table>
...

3.3 使用

各頁面表格操作列不一樣的使用 solt 插槽自定義

<CustomTable
  :tableData="tableData" tableCode="TransMasterTable" :oWidth="450" rowKey="id"
  rowId="id" :isChildren="true" @loadChildren="loadChildren" @onRefresh="onRefresh" @handleSelectionChange="handleSelectionChange">
  <!-- 操作列 -->
  <template #operate="{ scope }">
    <el-button type="text" @click="onTableOperate('details', scope)">查看</el-button>
    <el-button type="text" @click="onTableOperate('edit', scope)" :disabled="![0, 2, 3].includes(scope.tmDataState)">編輯</el-button>
    <el-button type="text" @click="onTableOperate('delete', scope)" :disabled="scope.tmDataState === 1 || scope.tmDataState === 4">刪除</el-button>
    <el-button type="text" @click="onTableOperate('copy', scope)">復制</el-button>
    <el-button type="text" @click="onTableOperate('submit', scope)" :disabled="scope.tmDataState !== 2">委托與提交</el-button>
    <el-button type="text" @click="onTableOperate('print', scope)" disabled>打印</el-button>
  </template>
</CustomTable>

四 表格自帶校驗

4.1 自定義 table 控件

CustomTableForm, 關鍵在於 TableColumnForm 組件

// CustomTableForm.vue
<a-table bordered :columns="columns" :data-source="dataSource" :rowKey="rowKey" :scroll="{ x: scrollX }" @resizeColumn="handleResizeColumn">
  <template #headerCell="{ column }">
    <span :class="[{'is-required': column.required}]">{{ column.title }}</span>
  </template>
  <template #bodyCell="{ column, record, index }">
    <template v-if="column.key === 'index'">
      <span>{{ index + 1 }}</span>
    </template>
    <template v-if="column.key === 'operate'">
      <!-- 是否需要自定義操作列 -->
      <template v-if="showDefaultOperate">
        <el-button type="text" @click="onTableOperate('save', record)" v-if="record.isEdit">保存</el-button>
        <el-button type="text" @click="onTableOperate('edit', record)" v-else>編輯</el-button>
        <el-button type="text" @click="onTableOperate('delete', record, index)" class="delete-button">刪除</el-button>
      </template>
      <!-- 插槽 -->
      <slot name="operate" :scope="record" :index="index"></slot>
    </template>
    <template v-if="column.key !== 'index' && column.key !== 'operate'">
      <!-- 核心 -->
      <TableColumnForm :column="column" :record="record"></TableColumnForm>
    </template>
  </template>
</a-table>
// TableColumnForm.vue
<template>
  <span v-if="!row.isEdit || !column.colType" @click="onclick">{{ row[column.key] }}</span>
  <template v-else>
    <el-tooltip class="item" effect="dark" :content="row[column.key]" placement="top" v-if="!column.colType || column.colType === 'button'">
      <el-button type="text" @click="onclick">{{ row[column.key] }}</el-button>
    </el-tooltip>
    <!-- 輸入框 -->
    <el-input
      v-if="!column.colType || column.colType === 'input'"
      v-model="row[column.key]"
      @blur="onBlur"
      :placeholder="column.placeholder"
      :class="[{'el-error': row.isError && !row[column.key] && column.required}]">
    </el-input>
    <!-- 下拉框 -->
    <el-select
      v-if="column.colType === 'select'"
      v-model="row[column.key]"
      :placeholder="column.placeholder"
      :class="[{'el-error': row.isError && !row[column.key] && column.required}]">
      <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
    </el-select>
    <!-- 日期選擇 -->
    <el-date-picker
      v-if="column.colType === 'date'"
      v-model="row[column.key]"
      type="date"
      value-format="YYYY-MM-DD"
      :disabled-date="disabledDate"
      :class="[{'el-error': row.isError && !row[column.key] && column.required}]"
      placeholder="Pick a date"
    >
    </el-date-picker>
    <!-- 數字輸入框 -->
    <el-input-number
      @blur="onBlur"
      @change="onChange"
      ref="inputNumberRef"
      v-if="column.colType === 'inputNumber'"
      v-model="row[column.key]"
      :min="column.min?column.min:''"
      controls-position="right" 
      :class="[{'el-error': row.isError && !row[column.key] && column.required}]" />
  </template>
</template>
 
<script lang="ts">
type optionItem = {
  label: string,
  value: string | number
} 
import { defineComponent, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
export default defineComponent({
  name: 'TableFormItem',
  props: {
    record: {
      type: Object,
      required: true
    },
    column: {
      type: Object,
      required: true
    }
  },
  setup(props) {
    const row = ref<any>({})
    const column = ref<any>({})
    const options = ref<optionItem[]>([])
    const inputNumberRef = ref<any | HTMLElement>(null)
    onMounted(() => {
      row.value = props.record
      column.value = props.column
      options.value = props.column.options
      if (props.column.colType === 'inputNumber' && row.value[props.column.key]) {
        row.value[props.column.key] = +row.value[props.column.key]
      }
    })
    const onBlur = (event) => {
      const targetNode = event.target
      // 有值 在去走校驗規則
      if (column.value.checkCallback && row.value[column.value.key as string]) {
        const flag = column.value.checkCallback(row.value[column.value.key as string].trim())
        if (!flag) {
          row.value[column.value.key as string] = ''
          targetNode.setAttribute('placeholder', props.column.message ? props.column.message : '必填項')
          ElMessage.warning(`${column.value.title}格式輸入有誤,請檢查`)
        } else {
          targetNode.setAttribute('placeholder', props.column.placeholder)
        }
      }
      // 無值且必填觸發
      if (!row.value[props.column.key as string] && props.column.required) {
        targetNode.setAttribute('placeholder', props.column.message ? props.column.message : '必填項')
        return targetNode.classList.add('el-checked-error')
      }
      if (row.value[props.column.key as string] && props.column.callBack) props.column.callBack(row.value)
      targetNode.classList.remove('el-checked-error')
    }
    const onclick = () => {
      if (row.value[props.column.key as string] &&  props.column.colType === 'button' && props.column.callBack){
        props.column.callBack(row.value)
      }
    }
    const onChange = () => {
      if (row.value[props.column.key as string] && props.column.callBack) props.column.callBack(row.value)
    }
    const disabledDate = (time: Date)=>{
      return time.getTime() > Date.now()
    }
    return { onBlur, row, options, onChange, onclick, inputNumberRef,disabledDate }
  }
})
</script>

4.2 使用

...
<CustomTableForm rowKey="systemGid" :scrollX="3600" :columns="columns" :dataSource="tableData" :showDefaultOperate="true"></CustomTableForm>
... 
<script lang="ts">
import { defineComponent, onMounted, reactive, toRefs } from 'vue'
import { useTableFormChecked } from '@/services/utils'
import { ElMessage } from 'element-plus'
// 下拉框數據源 用法1
const options1 = [
  {
    value: 'shenzhen',
    label: '深圳',
  },
  {
    value: 'guangzhou',
    label: '廣州',
  },
  {
    value: 'shanghai',
    label: '上海',
  },
  {
    value: 'beijing',
    label: '北京',
  }
]
// checkCallback 輸入校驗方法 用法1
 const nameChecked = (key) => {
  const reg = /^[\u4e00-\u9fa5]+$/
  return reg.test(key)
}

/**
 * @name 動態表格校驗方法
 * @param array table 數據   columns 表頭數據
 * @returns 
 */
 const useTableFormChecked = (array: any[], columns: any[]) => {
  let flag = false
  const titles = []
  array.forEach(t => {
    Object.keys(t).forEach((key) => {
      const item = columns.find(l => l.key === key)
      // 有值 但是 值格式對
      if (t[key] && item?.checkCallback && typeof item.checkCallback == 'function') {
        const reg = item.checkCallback(t[key])
        if (reg) return
        titles.push(item.title)
        flag = true
      }
      // 沒值 且 必填
      if (!t[key] && item?.required) {
        titles.push(item.title)
        flag = true
      }
      if ((!t[key] || t[key] === null) && t[key] !== 0 && t[key] !== false) t.isError = true
    })
  })
  return { flag: flag, titles: titles }
}

export default defineComponent({
  name: 'CustomTableFormContainer',
  setup() {
    const state = reactive({
      tableData: [{
        id: 1,
        name: '阿凡達',
        age: '18',
        gender: '女',
        weight: 88,
        country: '中國',
        city: 'shenzhen',
        province: '廣東',
        surname: '阿',
        address: '深圳前海嘉里',
        isEdit: false,
        isError: false
      }],
      columns: [
        {
          title: '序號',
          dataIndex: 'index',
          key: 'index',
          width: 60,
          minWidth: 60,
          resizable: true,
          fixed: 'left'
        },
        {
          title: '操作',
          dataIndex: 'operate',
          key: 'operate',
          width: 120,
          fixed: 'left'
        },
        {
          title: '名稱',
          dataIndex: 'name',
          key: 'name',
          resizable: true,
          width: 150,
          colType: 'input',
          required: true,
          checkCallback: nameChecked,
          placeholder: '請輸入名稱',
          message: '請輸入漢字字符'
        },
        {
          title: '年齡',
          dataIndex: 'age',
          key: 'age',
          resizable: true,
          width: 150,
          colType: 'input',
          required: true,
          checkCallback: null,
          placeholder: '請輸入名稱'
        },
        {
          title: '性別',
          dataIndex: 'gender',
          key: 'gender',
          resizable: true,
          width: 150,
          colType: 'select',
          required: true,
          placeholder: '請選擇性別',
          options: null
        },
        {
          title: '體重',
          dataIndex: 'weight',
          key: 'weight',
          resizable: true,
          required: true,
          width: 150,
          min: 80,
          max: 150,
          colType: 'inputNumber',
        },
        {
          title: '姓',
          dataIndex: 'name',
          key: 'name',
          resizable: true,
          width: 120,
          placeholder: '請輸入名稱'
        },
        {
          title: '地址',
          dataIndex: 'surname',
          key: 'surname',
          resizable: true,
          width: 150,
          placeholder: '請輸入地址'
        },
        {
          title: '國家',
          dataIndex: 'country',
          key: 'country',
          width: 120,
          resizable: true,
          placeholder: '請輸入國家'
        },
        {
          title: '省',
          dataIndex: 'province',
          key: 'province',
          width: 120,
          resizable: true,
          placeholder: '請輸入省'
        },
        {
          title: '城市',
          dataIndex: 'city',
          key: 'city',
          resizable: true,
          width: 120,
          colType: 'select',
          options: options1
        }
      ],
      options: [
        {
          value: 'Option1',
          label: 'Option1',
        },
        {
          value: 'Option2',
          label: 'Option2',
        },
        {
          value: 'Option3',
          label: 'Option3',
        },
        {
          value: 'Option4',
          label: 'Option4',
        },
        {
          value: 'Option5',
          label: 'Option5',
        },
      ]
    })
    onMounted(() => {
      // checkCallback 輸入校驗方法 用法2
      const item = state.columns.find(t => t.key === 'age')
      item.checkCallback = ageChecked

      // 下拉框數據源 用法2
      const item1 = state.columns.find(t => t.key === 'gender')
      item1.options = state.options
    })
    const ageChecked = (key) => {
      const reg = /[^\d]/g
      return !reg.test(key)
    }
    // 添加一行
    const onOperate = () => {
      const index = state.tableData[state.tableData.length - 1].id
      state.tableData.push({
        id: index + 1,
        name: '',
        age: '',
        gender: '',
        country: '',
        city: '',
        province: '',
        surname: '',
        address: '',
        weight: 80,
        isEdit: true,
        isError: false
      })
    }
    const onSave = () => {
      const { flag, titles } = useTableFormChecked(state.tableData, state.columns)
      // 攔截 校驗沒過禁止往下走
      if (flag) return ElMessage.warning(`表格數據(${titles.toString()})輸入有誤,請檢查`)
      // 下面是保存邏輯 .....
      ElMessage.success('保存成功')
    }
    return { ...toRefs(state), onOperate, onSave }
  }
})
</script>

4.3 使用文檔

// README.md
## 源文件
  -  `CustomTableForm`
  -  `TableColumnForm` 

## 事例參考
  - 路由 `order-manage/customTableFormContainer`
  - 頁面 `CustomTableFormContainer`

## 表格數據表單使用規則 (適應需求: 表格動態添加數據  --  和表單一樣帶校驗)
## 使用方式  A 代表必填, B 非必填
  - `tableCode` (B):`用於動態獲取標表頭; 當需要右上角布局、自定義列顯示按鈕時, 為必填項`
  - `columns` (A) : `表頭數據源`
    -  `title` (A):  `表頭名稱`
    -  `key`(A): `Vue 需要的 key / 列數據在數據項中對應的路徑 -- 唯一值`
    -  `resizable` (B): , `是否需要自定義列寬 -- 拖動列寬` 
    -  `width` (B): `150`, `列初始寬度; 注:resizable 為true時 width必填`
    -  `required` (B): `是否必填, true 會為表頭加上紅色 *`,
    -  `placeholder` (B): `輸入框以及下拉框的提示`,
    -  `min` (B): `colType 為 inputNumber 數字輸入框時最小值`,
    -  `max` (B): `colType 為 inputNumber 數字輸入框時最大值`,
    -  `message` (B): `校驗提示`,
    -  `colType` (B): `動態表格項類型 默認不傳 span; button: 提供點擊回調; input: 輸入框; select: 下拉框; inputNumber: 數字輸入框`,
    -  `checkCallback` (B): `必填時自定義的校驗規則回調函數; 使用方式參考事例`,
    -  `callBack` (B): `方法回調 使用方式參考交易單商品添加`,

  - `dataSource` (A): 列表數據
    - 注: 要和 `columns key` 一一對應

  - `rowKey` (A): `dataSource` 每一項的唯一標識
  - `tableCode` (B): 需要修改列寬的時候后台提供的表頭接口標識
  - `scrollX` (B): 列表橫向寬度
  - `scrollY` (B): 列表縱向高度
  - `showDefaultOperate` (B): 是否顯示默認操作列; 值為false 時需要傳入自定義的操作列,
## 提示

  - 改  `CustomTableForm` / `TableColumnForm` 文件之前 請先熟悉這 2 文件
  - 更改時間建議寫在下面
  - 有問題微信聯系我

## 更改日志
  - `12-14` - `laisheng` :使用方式添加

寫的有點亂。有需要的請自行取舍; 這里大概介紹的就是思路及步驟


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM