vue3+antd项目总结

本文介绍了在Vue3项目中,如何在main.ts中引入框架、路由和防抖指令,特别是防抖指令的实现。同时,讨论了动态表单的循环绑定校验规则,以及在AntDesignVue中处理表格数据预处理、下拉树选中项为对象、搜索功能、可编辑表格焦点和固定列宽等问题。文章还探讨了Vue3的`<scriptsetup>`语法糖、reactive和ref的区别与应用。
摘要由CSDN通过智能技术生成

1、main.ts中引入项目所需框架、路由、防抖指令

 2、防抖指令方法

export default (app: any) => {
  app.directive('preReClick', {
    mounted(el: any, binding: any) {
      el.addEventListener('click', () => {
        if (!el.disabled) {
          el.disabled = true;
          setTimeout(() => {
            el.disabled = false;
          }, binding.value || 2000)
        }
      })
    }
  })
}

3、App.vue中引入antd中文包

遇到的问题:

1、动态表单循环绑定校验规则

:name="['transGroups',i,'clsList',indexc,'match','conditions',index,'ele']"
等同于模板字符串写法
:name="`transGroups[${i}].clsList[${indexc}.match.conditions[${index}.ele]]`"
不过在antd里就要用数组的方式体现

官方示例:

动态增减表单项 - ant-design-vue@3.2.20 - CodeSandbox

 2、表格数据预处理

使用customRender

 3、下拉树使用labelInValue选中项为对象

多选下拉树:@change时value的打印结果

 不设置时打印结果:

4、下拉树可搜索

treeNodeFilterProp="text" 

5、可编辑表格自动聚焦

6、动态可编辑表格中固定列宽需设置内部表单中input/select框的宽度,在<a-table-column>中设置不生效

表格组件

<template>
  <div class="consign-components__generalTable_container">
    <!-- 按钮区 -->
    <div class="button-area">
      <headerButton :button-array="_functionButtons" class="ml--8"></headerButton>
      <headerButton :button-array="commonButton"></headerButton>
    </div>
    <!-- 搜索区 -->
    <div class="search-layout">
      <slot name="searchForm" class="search-form"></slot>
      <slot name="searchFormButton">
        <div class="search-button">
          <a-button type="primary" @click="search" v-preReClick>查询</a-button>
          <a-button class="ml-8" @click="reset" v-preReClick>重置</a-button>
        </div>
      </slot>
    </div>
    <!-- 表格区 -->
    <slot name="tableTitle"></slot>
    <a-config-provider :locale="zhCN">
      <a-table
          class="ant-table-striped"
          :row-class-name="rowClassName"
          :dataSource="_dataSource"
          :columns="_columns"
          size="small"
          bordered
          :row-selection="{
            selectedRowKeys: selectedRowKeys,
            onChange: onSelectChange,
            type:noMultiple?'radio':'checkbox'
          }"
          :rowKey="rowKey"
          :custom-row="customRow"
          :custom-header-row="customHeaderRow"
          :pagination="_pagination"
          :scroll="{ y: `calc(100vh - ${scrollY})` }"
      >
        <template #bodyCell="{ column, text, record }">
          <slot name="bodyCell" v-bind="{ column, text, record }"></slot>
        </template>
      </a-table>
    </a-config-provider>
  </div>
</template>

<script setup lang="ts">
import {computed, reactive, ref, toRefs, watch} from 'vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';

import HeaderButton from './headerButton.vue';

const props = defineProps({
  functionButtons: {
    type: Array,
    default: () => [],
  },//功能按钮区
  noExport: {
    type: Boolean,
    default: false
  },//不显示导出按钮
  commonButton: {
    type: Array,
    default: () => [
      {
        type: 'default',
        text: '查询规则',
        disabled: false,
        method: () => {
        },
      },
      {
        type: 'default',
        text: '显示字段',
        disabled: false,
        method: () => {
        },
      },
    ]
  },//通用按钮区
  // 表格区
  dataSource: {
    type: Array as () => Array<object>,
    default: () => [],
  },//表格数据
  columns: {
    type: Array,
    default: () => [],
  },//表格行
  rowKey: {
    type: String,
    default: "id"
  },//表格rowKey
  pagination: {
    type: Object,
    default: () => ({})
  },//表格分页
  scrollY: {
    type: String,
    default: '340px'
  },//表格纵向滚动条
  noMultiple: {
    type: Boolean,
    default: false
  },//单选时传递
  noClearSelectedRowKeys: {
    type: Boolean,
    default: false
  },//默认情况下监听dataSource变化置空selectedRowKeys,该属性用于手动置空的情况,初始值需设置为true
});
const {
  functionButtons,
  noExport,
  commonButton,
  dataSource,
  columns,
  pagination,
  rowKey,
  scrollY,
  noMultiple,
  noClearSelectedRowKeys
} = toRefs(props);

const exportButton = {
  type: 'primary',
  ghost: true,
  text: '导出',
  disabled: false,
  method: () => {
  },
};
const _functionButtons = computed(() => [...functionButtons?.value, ...(noExport?.value ? [] : [exportButton])]);

const emits = defineEmits(['getTableSelectedRowKey', 'search', 'reset', 'update:noClearSelectedRowKeys']);


// 表格区
function search() {
  emits('search')
}

function reset() {
  emits('reset')
}

interface IPagination {
  current: number;
  pageSize: number;
  total: number;
  showTotal: (total: any, range: any) => string;
  showSizeChanger: boolean;
  showQuickJumper: boolean;
  onChange: (page: any, pageSize: any) => void;
}

// 分页
const defaultPagination = reactive<IPagination>({
  current: 1,
  pageSize: 50,
  total: 0,
  showTotal: (total, range) => `显示${range[0]}到${range[1]},共${total}记录`,
  showSizeChanger: true,
  showQuickJumper: true,
  onChange: (page, pageSize) => {
    console.log(page, pageSize)
  }
})

const _pagination = computed(() => {
  if (!pagination?.value) {
    return false;
  }
  Object.assign(defaultPagination, pagination?.value)
  return defaultPagination
})

// 为列表添加序号的显示
const _dataSource = computed(() => {
  const {current, pageSize} = _pagination.value as IPagination
  return dataSource?.value?.map((ele, index) => {
    return {
      ...ele,
      index: index + 1 + (current - 1) * pageSize,
    };
  });
});

const _columns = computed(() => {
  const indexColumn = {title: '#', dataIndex: 'index', ellipsis: true, align: 'center', width: '50px'}
  if ((columns?.value as Array<any>).find(ele => ele.dataIndex === 'index')) {
    return columns?.value
  }
  return [indexColumn, ...columns?.value]
})


type Key = string | number;
const selectedRowKeys = ref<Key[]>([]);

watch(
    () => dataSource?.value,
    (n, v) => {
      if (noClearSelectedRowKeys.value) return
      selectedRowKeys.value = []
      emits('update:noClearSelectedRowKeys', true)
    },
    {deep: true}
)

watch(
    () => selectedRowKeys.value,
    (n, v) => {
      emits('getTableSelectedRowKey', n);
    },
    {deep: true},
);

const onSelectChange = (keys: Key[]) => {
  selectedRowKeys.value = keys;
};

// 当点击行时将整行选中
const customRow = (record, index) => {
  return {
    onClick: () => {
      selectRow(record, index);
    },
  };
};
const customHeaderRow = (columns, index) => {
  return {
    onClick: () => {
    },
    class: 'custom-header-row'
  }
}

// 斑马纹
const rowClassName = (_record, index) => (index % 2 === 1 ? 'table-striped' : null)

const selectRow = (record, index) => {
  const currentId = record[rowKey.value]
  if (selectedRowKeys.value.includes(currentId)) {
    selectedRowKeys.value = selectedRowKeys.value.filter((ele) => ele !== currentId);
  } else {
    if (noMultiple?.value) {
      selectedRowKeys.value = [currentId]
    } else {
      selectedRowKeys.value.push(currentId);
    }
  }
};
</script>

<style scoped></style>

vue3中的语法糖

<script setup> 是在单文件组件(SFC)中使用组合式API的编译时语法糖,解决Vue3.0中setup需要频繁将声明的变量、函数以及import引入的内容通过return向外暴露,才能在<template/>使用的问题

基本用法

<script setup>
// import 引入内容
import { getToday } from './utils'
// 变量
let msg = 'Hello!'
// 函数
function log(){ 
  console.log(msg)
}
</script>
// 在template中直接使用声明的变量、函数以及import引入的内容
<template>
  <div @click="log">{{ msg }}</div>
  <p>{{ getToday() }}</p>
</template>

总结:<script setup> 语法糖里面的代码会被编译成组件setup()函数的内容,不需要通过return暴露声明的变量、函数以及import引入的内容,即可在<template/>使用,并且不需要些export default{}

这意味着与普通的 <script> 只在组件被首次引入的时候执行一次不同,<script setup> 中的代码会在每次组件实例被创建的时候执行

  <script>
  console.log('script');//多次实例组件,只触发一次
  export default {
      setup() {
          console.log('setupFn');//每次实例化组件都触发和script-setup标签一样
      }
  }
  </script>

reactive和ref

reactive函数:
  • 前置说明:
    • setup需要有返回值,只有返回的值才能在模板中使用
    • 默认普通的数据是非响应式的
  • 作用:
    •  创建原始对象的响应式副本,即将【引用类型】数据转换为【响应式数据】(返回该对象的响应式代理)
  • 参数:
    • reactive参数必须是对象或数组
  • 函数实现
    • // 判断是否为对象
      const isObject = val => val !== null && typeof val === 'object';
      // 判断key是否存在
      const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
      
      export function reactive(target) {
          // 首先先判断是否为对象
          if (!isObject(target)) return target;
      
          const handler = {
              get(target, key, receiver) {
                  console.log(`获取对象属性${key}值`)
                  // 收集依赖 ...
                  const result = Reflect.get(target, key, receiver)
                  // 深度监听(惰性)
                  if (isObject(result)) {
                      return reactive(result);
                  }
                  return result;
              },
      
              set(target, key, value, receiver) {
                  console.log(`设置对象属性${key}值`)
      
                  // 首先先获取旧值
                  const oldValue = Reflect.get(target, key, reactive)
      
                  let result = Reflect.set(target, key, value, receiver);
                  
                  if (result && oldValue !== value) {
                      // 更新操作 ...
                  }
                  return result
              },
      
              deleteProperty(target, key) {
                  console.log(`删除对象属性${key}值`)
      
                  // 先判断是否有key
                  const hadKey = hasOwn(target, key)
                  const result = Reflect.deleteProperty(target, key)
      
                  if (hadKey && result) {
                      // 更新操作 ...
                  }
      
                  return result
              },
              
              // 其他方法
              // ...
          }
          return new Proxy(target, handler)
      }
      
      const obj = { a: { b: { c: 6 } } };
      const proxy = reactive(obj);
      
      proxy.a.b.c = 77;
      
      // 获取对象属性a值
      // 获取对象属性b值
      // 设置对象属性c值 77

      至此,引用类型的对象我们已经可以把它转化成响应式对象了,Proxy对象只能代理引用类型的对象,对于基本数据类型如何实现响应式呢?

      vue的解决方法是把基本数据类型变成一个对象:这个对象只有一个value属性,value属性的值就等于这个基本数据类型的值。然后,就可以用reative方法将这个对象,变成响应式的Proxy对象。

      实际上就是: ref(0) --> reactive( { value:0 })

    • 数据变更时使用Object.assign(obj,res.data)动态更新数据

  • 总结: 通常是用来定义响应式 对象数据
ref函数

reactive处理的数据,必须是复杂类型,如果是简单类型则无法处理成响应式,所以有ref函数

  • 作用:把基本类型的数据变为响应式数据
  • 参数:
    • 基本数据类型
    • 引用类型
    • DOM的ref属性值
  • 函数实现
    • export function ref(value?: unknown) {
        return createRef(value, false)
      }
      
      function createRef(rawValue: unknown, shallow: boolean) {
        if (isRef(rawValue)) {
          return rawValue
        }
        return new RefImpl(rawValue, shallow)
      }
      
      class RefImpl<T> {
        private _value: T
        private _rawValue: T
      
        public dep?: Dep = undefined
        public readonly __v_isRef = true
      
        constructor(value: T, public readonly __v_isShallow: boolean) {
          this._rawValue = __v_isShallow ? value : toRaw(value)
          this._value = __v_isShallow ? value : toReactive(value)
        }
      
        get value() {
          trackRefValue(this)
          return this._value
        }
      
        set value(newVal) {
          const useDirectValue =
            this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
          newVal = useDirectValue ? newVal : toRaw(newVal)
          if (hasChanged(newVal, this._rawValue)) {
            this._rawValue = newVal
            this._value = useDirectValue ? newVal : toReactive(newVal)
            triggerRefValue(this, newVal)
          }
        }
      }

      大体思路就是,调用ref函数时会new一个类,这个类监听了value属性的get和set,实现了在get中收集依赖,在set中触发依赖,而如果需要对传入参数深层监听的话,就会调用我们上面提到的reactive方法。

      即:
    • ref(0); // 通过监听对象(类)的value属性实现响应式
      ref({a: 6}); // 调用reactive方法对对象进行深度监听

      ref 方法包装的数据,需要使用.value 来访问,但在模板中不需要,Vue解析时会自动添加。

区别:

  • reactive 将引用类型值变为响应式,使用Proxy实现
  • ref 可将基本类型和引用类型都变成响应式,通过监听类的value属性的getset实现,但是当传入的值为引用类型时实际上内部还是使用reactive方法进行的处理
  • 推荐基本类型使用ref,引用类型使用 reactive

                        

Vue3是一个流行的前端框架,具有高效的渲染和响应能力。antd是一个基于Vue的UI组件库,提供了丰富的组件和样式。Vite是一个新的打包工具,具有更快的启动和热更新速度。 搭建一个后台项目,可以使用以下步骤: 1. 安装Node.js和npm,确保全局安装了@vue/cli。 2. 使用命令行工具创建一个新的Vue3项目vue create my-project。 3. 选择手动配置,并选择Babel,Router,CSS Pre-processors,ESLint和Linter / Formatter。 4. 安装antd:npm install ant-design-vue@next。 5. 在src/main.js中引入antd的样式和组件:import { createApp } from 'vue'; import App from './App.vue'; import Antd from 'ant-design-vue'; import 'ant-design-vue/dist/antd.css'; 6. 在创建应用程序之前使用Antd组件:const app = createApp(App); app.use(Antd); app.mount('#app'); 7. 使用vite创建一个新的项目文件夹:mkdir my-project && cd my-project。 8. 在项目文件夹中初始化npm:npm init -y。 9. 安装vite:npm install vite。 10. 创建vite配置文件:npx create-vite。 11. 安装其他依赖:npm install axios vuex。 12. 在src/main.js中引入antd的样式和组件:import { createApp } from 'vue'; import App from './App.vue'; import Antd from 'ant-design-vue'; import 'ant-design-vue/dist/antd.css'; 13. 在创建应用程序之前使用Antd组件:const app = createApp(App); app.use(Antd).use(router).use(store).mount('#app')。 14. 运行开发服务器:npm run dev。 这样,你就成功搭建了一个使用Vue3、antd和vite的后台项目。你可以根据项目需求进行开发,并根据需要引入和使用更多的antd组件。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值