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属性的get
和set
实现,但是当传入的值为引用类型时实际上内部还是使用reactive
方法进行的处理- 推荐基本类型使用
ref
,引用类型使用reactive