配置主题
scss写法
src/styles/theme.variable.scss
$primaryColor: #1677ff;
$white: #ffffff;
$black: #000000;
导出给themeConfig.ts使用 src/styles/theme.module.scss
:export {
themeColor: $primaryColor;
}
src/styles/themeConfig.ts
/**
* 主题色
* 用途1: ant-d 组件库的主题
* 用途2: tailwind 的预设变量
* 用途3: less 的预设变量
*/
import variables from './theme.module.scss'
const themeConfig = {
token: {
colorPrimary: variables.themeColor
}
} as const
// Object.freeze(themeConfig)
export default themeConfig
less写法
src/styles/themeConfig.ts
/**
* 主题色
* 用途1: ant-d 组件库的主题
* 用途2: tailwind 的预设变量
* 用途3: less 的预设变量
*/
const themeConfig = {
token: {
colorPrimary: '#296bcb',
colorSuccess: '#56b460',
colorWarning: '#e8b533',
colorError: '#e83345',
colorTextBase: '#c1d5ff',
colorBgContainer: '#21293d',
colorBgElevated: '#0b162f',
colorBgSpotlight: '#293550',
colorBgBase: '#1c2d46',
fontSize: 16,
wireframe: false,
colorPrimaryBg: '#296bcb',
colorPrimaryText: '#94fcfd',
colorPrimaryTextActive: '#94fcfd',
borderRadius: 1,
borderRadiusSM: 4,
borderRadiusLG: 6,
borderRadiusXS: 2,
// 自定义颜色表
colorWhite: '#fff',
colorWhite2: '#E3E4E4',
colorBlue1: 'rgba(41, 107, 203, 0.15)',
colorBlue2: 'rgba(41, 107, 203, 0.3)',
colorBlue3: '#0E477B',
colorBlue4: '#AAD1F3',
colorBlue5: '#B2C0CD',
colorBlue6: '#37DBF3',
colorBlue7: '#3e82e4',
colorBlue8: '#0B1730',
colorBlue9: 'rgba(11, 22, 47, 0.80)',
colorBlue10: '#283960',
colorBlue11: '#66C4FF',
colorBlue12: '#74b2ff',
colorBlue13: '#0C1932',
colorBlue14: 'rgb(102, 196, 255, 0.1)',
colorBlue15: 'rgb(102, 196, 255, 0.2)',
colorBlue16: 'rgb(102, 196, 255, 0.4)',
colorBlue17: 'rgb(193, 213, 255, 0.90)',
colorBlue18: 'rgb(33, 96, 172, 0.30)',
colorBlue19: 'rgb(116, 178, 255, 0.4)',
colorPurple1: '#8899FA',
colorRed1: 'rgba(232, 51, 69, 0.20)',
colorRed2: 'rgba(232, 51, 69, 0.10)',
colorRed3: 'rgba(232, 51, 69, 0.40)',
colorRed4: '#FFC2C8',
colorYellow1: 'rgba(232, 181, 51, 0.10)',
colorYellow2: 'rgba(232, 181, 51, 0.20)',
colorYellow3: 'rgba(232, 181, 51, 0.40)',
// 自定义渐变色表
colorLinearGradient1: 'linear-gradient(180deg, #ECF9FF 0%, #90D8FC 100%)',
colorLinearGradient2: 'linear-gradient(180deg, #296bcb 0%, #1553ad 100%)',
colorLinearGradient3: 'linear-gradient(180deg, #153C73 0%, #153C73 100%)',
colorLinearGradient4:
'linear-gradient(180deg, #FF1321 0%, #F6EA32 38%, #25C970 70.5%, #132AFF 100%)',
colorLinearGradient5: 'linear-gradient(180deg, #E6F7FF 0%, #3CB1FB 100%)',
},
algorithm: [null],
} as const;
Object.freeze(themeConfig);
export default themeConfig;
/src/App.vue
<script lang="ts" setup>
import themeConfig from '@/design/themeConfig';
import { theme, legacyLogicalPropertiesTransformer } from 'ant-design-vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import { RouterView } from 'vue-router';
defineOptions({ name: 'App' });
const {
VITE_PREFIX_CLASSNAME,
} = import.meta.env;
// antD 全局配置
const antDConfig = {
locale: zhCN,
prefixCls: VITE_PREFIX_CLASSNAME,
componentSize: 'middle',
theme: {
...themeConfig,
algorithm: [theme.darkAlgorithm, theme.compactAlgorithm],
},
} as const;
</script>
<template>
<a-style-provider hash-priority="high" :transformers="[legacyLogicalPropertiesTransformer]">
<a-config-provider v-bind="antDConfig">
<RouterView />
</a-config-provider>
</a-style-provider>
</template>
<style>
@import '@/design/antd.less';
</style>
<style module>
.app {
@apply h-screen w-screen text-TextBase select-none pointer-events-none fullAbsolute;
font-size: 0.875rem;
}
</style>
配置命名空间
/.env
# antd 的样式名前缀
VITE_PREFIX_CLASSNAME = GD_DKY
/vite.config.ts
import { fileURLToPath, URL } from 'node:url';
import { defineConfig, loadEnv } from 'vite';
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
// @ts-ignore
import themeConfig from './src/design/themeConfig';
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd(), '');
const { VITE_PREFIX_CLASSNAME } = env;
return {
css: {
preprocessorOptions: {
// scss: {
// javascriptEnabled: true,
// additionalData: `@import "@/styles/theme.variable.scss";$antNamespace: ${env.VITE_PREFIX_CLASSNAME};`
// },
less: {
javascriptEnabled: true,
globalVars: {
...themeConfig.token,
antNamespace: VITE_PREFIX_CLASSNAME,
},
},
},
},
};
});
定制组件样式
/src/styles/antd.scss
less 命名空间是 @{antNamespace}
/**
定制 ant-d-vue 的组件样式
*/
#app {
.#{$antNamespace} {
// 分段控制器(tab)
&-segmented {
// background-color: $colorBlue13;
// font-size: 0.875rem;
&-item {
// border-radius: 1px;
&:hover,
&-selected {
// color: $colorPrimaryTextActive;
&:not(:hover) {
// background-color: $colorPrimaryBg;
}
}
}
}
// 下拉选择器
&-select {
&-selection-item {
&::after {
// color: $colorWhite;
}
}
&-dropdown {
// border-radius: 2px;
// border: 1px solid $colorBlue3;
}
&-item-option {
// border-radius: 2px;
&-active,
&-selected {
//background: $colorPrimaryBg ;
// color: $colorPrimaryText;
}
}
}
// 日期选择器
&-picker {
&-dropdown {
// border-radius: 2px;
// border: 1px solid $colorBlue3;
}
}
// 播放条
&-slider {
&-rail {
// background-color: $colorBlue13;
}
&-track,
&-handle::after {
// background-color: $colorWhite2;
}
&-handle {
// top: 50%;
// transform: translate(-50%, -50%);
}
}
// 表格
&-table {
background: unset;
&-row {
&:hover {
td {
background: unset;
}
}
td {
background: unset;
}
}
&-cell {
// padding: 8px 16px;
// background: unset;
border: unset;
a {
// color: $colorPrimaryText;
}
&-fix-left {
// background: unset;
}
}
&-header {
border-radius: 0;
}
&-thead {
.#{$antNamespace}-table-cell {
border-bottom: none;
// background: $colorBgContainer;
&::before {
display: none;
}
// 圆角实现
&:first-child {
border-top-left-radius: 0px; /* 左上角圆角 */
border-bottom-left-radius: 0px; /* 右上角圆角 */
}
&:last-child {
border-top-right-radius: 0px; /* 左上角圆角 */
border-bottom-right-radius: 0px; /* 右上角圆角 */
box-shadow: unset;
}
}
}
}
// 按钮
&-btn {
// padding-left: 16px;
// padding-right: 16px;
// height: 32px;
// box-shadow: unset;
& > span {
// display: inline-flex;
}
}
&-input-affix-wrapper {
// height: 32px;
}
// 输入框统一宽度、高度
&-input-affix-wrapper,
&-select {
//width: 240px;
//height: 32px;
// min-width: 80px;
// background: $colorBlack1;
input,
.#{$antNamespace}-select-selector {
// background: unset;
// height: 100%;
// align-items: center;
}
}
// 表单
&-form {
&-item-label > label {
// height: 100%;
}
}
// 分页
&-pagination {
.#{$antNamespace}-select {
// width: auto;
// height: auto;
}
&-total-text {
// flex: 1 1 auto;
// margin-left: 10px;
}
}
// 通知
&-notification-notice {
// border-radius: 4px;
// border-left-width: 4px;
// border-left-color: transparent;
&-error {
// border-left-color: $colorError;
}
&-success {
// border-left-color: $colorSuccess;
}
&-info {
// border-left-color: $colorPrimary;
}
&-warning {
// border-left-color: $colorWarning;
}
}
// 菜单
&-menu {
// border-right: none;
&-item {
// height: 40px;
// display: inline-flex;
// align-items: center;
// padding: 0 12px;
// border-radius: 2px;
// margin: 2px 0;
&-selected {
// color: $colorPrimaryTextActive;
}
&-icon {
// margin-right: 10px;
}
}
}
// 抽屉
&-drawer {
&-body {
// padding: 20px 24px;
}
&-content-wrapper {
// border-left: 1px solid $colorBlue21;
}
&-header {
// padding: 24px;
// border-color: $colorBlue21;
&-title {
// height: 28px;
}
}
&-title {
// color: $colorWhite;
// font-size: 20px;
// font-weight: normal;
}
&-close {
// color: $colorWhite;
// order: 1;
// margin-left: 16px;
}
}
// 锚点导航
&-anchor {
&-link {
& > a {
// color: $colorWhite;
}
&-active > a {
// color: $colorPrimaryTextActive;
}
}
&-ink-visible {
// width: 1px;
// background-color: $colorPrimaryTextActive;
}
&::before {
// border-left: 1px $colorBlue21 solid;
}
&-wrapper {
}
}
// 描述列表
&-descriptions {
&-title {
// color: $colorWhite;
}
//&-view {
// border-radius: 2px;
//}
&-item {
&-content {
// color: $colorWhite;
}
&-label {
// background-color: $colorBlue22;
// color: $colorWhite3;
}
}
}
// 文本
&-typography {
// margin-bottom: 0;
&-ellipsis {
// white-space: normal;
}
}
// 多选框
&-checkbox {
&:not(&-checked) .#{$antNamespace}-checkbox-inner {
// border-color: $colorBlue24;
}
}
// 数字角标
&-badge-count {
// background-color: $colorRed5;
// color: $colorWhite;
}
// 时间线
&-timeline-item-tail {
// top: 10px;
// left: 4px;
}
}
}
修改默认样式
需要用到vue sytle 的module属性
直接写module 默认是$style, 写module="$glob" 就是$glob
单个类名用:global(){}
多个类名用:global{}
less 命名空间是 @{antNamespace}
<template>
<div :class="$style.progress">
<!-- 标题 -->
<div class="flex justify-between items-center">
<span>{{ label }}</span>
<span>{{ value }}</span>
</div>
<!-- 进度条 -->
<a-progress class="bg-white" :percent="percent" :show-info="false" />
</div>
</template>
<style lang='less' module>
.progress {
/* 单个用:global(){}
:global(.#{$antNamespace}-progress) {
&-bg {
background-color: #aff9ff !important;
}
}
/* 多个用:global{}
:global{
.red {
color: red;
}
.#{$antNamespace}-progress {
&-bg {
background-color: #aff9ff !important;
}
}
}
}
</style>
自动按需引入组件
npm install unplugin-vue-components -D /vite.config.js
import { defineConfig } from 'vite';
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
// ...
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
}),
],
});
1. 表格滚动
需要给表格的父容器固定宽
给a-table 绑定 :scroll="{ y: scrollY, x: 'max-content' }" 属性,
scrollY是计算属性, 通过设置flex, 纵向排列, 给表格设置flex: 1,overflow-hidden 计算表格的容器高度clientHeight, 盒子有padding 要减去上下padding
给列配置某些项绑定具体宽度 width: 200
<div class="flex flex-col">
<div ref="containerRef" class="flex-1 overflow-hidden">
<a-table pagination="{}" v-model:current="currentPage" v-model:pageSize="pageSize" @change="pageChange" scrollToFirstRowOnChange :scroll="{ y: scrollY - 40, x: 'max-content' }"
:columns="service_demand_column" :data-source="tableData">
<template #bodyCell="{ column, text, record }">
<!-- 详情 -->
<template v-if="column.dataIndex === 'operation'">
<a-button type="link" @click="() => clickDetail(record)">详情</a-button>
</template>
<!-- 等级 -->
<template v-else-if="column.dataIndex === 'warningLevel'">
<span :class="`text-[${colorList[text]}]`">{{ text || '-' }}</span>
</template>
<!-- 其他列 -->
<template v-else>
{{ text || '-' }}
</template>
</template>
</a-table>
</div>
</div>
import useTableHeight from '@/hooks/useTableHeight.hook'
const { containerRef, scrollY } = useTableHeight()
export const service_demand_column = [
{ title: '编号', dataIndex: 'orderNo', fixed: 'left' },
{ title: '等级', dataIndex: 'warningLevel' },
{ title: '分组', dataIndex: 'businessGrouping' },
{ title: '子类', dataIndex: 'businessSub' },
{ title: '发布时间', dataIndex: 'warningTime' },
{ title: '单位', dataIndex: 'powerUnit', width: 100 },
{ title: '发布对象', dataIndex: 'warningObject', width: 120 },
{ title: '销单时间', dataIndex: 'cancelOrderTime' },
{ title: '操作', dataIndex: 'operation', fixed: "right", align: 'center' },
];
// 计算表格高度
import { computed } from 'vue'
const useTableHeight = () => {
const prefixClassname = import.meta.env.VITE_PREFIX_CLASSNAME
const headerClassName = `.${prefixClassname}-table-header`
const paginationClassName = `.${prefixClassname}-table-pagination`
const containerRef = ref()
const scrollY = computed<number>(() => {
const { clientHeight } = containerRef.value || {}
const header = containerRef.value?.querySelector?.(headerClassName)
const pagination = containerRef.value?.querySelector?.(paginationClassName)
const { clientHeight: headerHeight } = header || {}
const { clientHeight: paginationHeight } = pagination || {}
return (clientHeight ?? 0) - (headerHeight ?? 0) - (paginationHeight ?? 0)
})
return { containerRef, scrollY }
}
export default useTableHeight
2. 表格里配置分页配置
由于a-table默认带了分页pagination, 也可以 :pagination="false"屏蔽
但是用的时候想配页码页数就要加配置
写成 :pagination="{}"的形式
页码改变调用change回调, 自定义参数后不调用change无法改变页码
<a-table :pagination="pagination" @change="pageChange"
:scroll="{ y: 650, x: 'max-content' }" class="mt-[20px]" :columns="service_demand_column" :data-source="tableData">
...
</a-table>
// 分页参数
const pagination = reactive({
current: 1,
pageSize: 15,
total: 0
})
3. 表格根据配置生成列
ant 的表格直接支持传入 columns 配置, 不像element需要自己遍历 columns 里也支持 customRender 渲染自定义的jsx, 但如果要绑定事件就需要把那一项单独在vue组件添加了
// 在配置文件columnData.tsx中
export const energy_statistics_column = [
{ title: '能源名称', dataIndex: 'energyName' },
{ title: '所属单位', dataIndex: 'unit' },
{ title: '发电量(kW·h)', dataIndex: 'powerQuantity' },
{ title: '投运日期', dataIndex: 'commissioningDate' },
{
title: '运行状态', dataIndex: 'commissioningStatus', align: 'center',
customRender: ({ text, record, index, column }) => {
return (
<Button type="primary" size="small"
style={{ boxShadow: 'none', background: energy_statistics__list[text][1], color: energy_statistics__list[text][0] }}>{
text || '-'
}</Button>
);
}
},
];
// 在使用组件 xx.vue中
<!-- 表格 -->
<a-table :pagination="pagination" @change="pageChange" scrollToFirstRowOnChange
:scroll="{ y: 490, x: 'max-content' }" class="mt-[20px]" :columns="columnData"
:data-source="tableData">
</a-table>
<script setup lang='tsx'>
import { energy_statistics_column } from '@/data/columnData';
const columnData = [...energy_statistics_column, {
title: '操作',
align: 'center',
dataIndex: 'operation',
fixed: "right",
customRender: ({ text, record, index, column }) => {
return (
<a-button type="link" onClick={() => clickDetail(record)}>
详情
</a-button>
);
},
}]
/**
* 点击表格详情
*/
const clickDetail = (record) => {
}
</script>
4. watch监听表格筛选项、搜索项、页码页数改变, 统一调用请求表格数据
- 给a-table绑定页码页数参数, change更改参数
- computed计算请求参数, 清除对象空值
- watch监听页码和请求参数, 触发请求函数
- 请求表格数据
<!-- 筛选表单 -->
<FormFilter class="w-[240px]" :formData="formData">
<a-button class="mr-[10px]" type="primary">确定</a-button>
<a-button color="#e7e7e7" @click="resetFilter">重置</a-button>
</FormFilter>
<!-- 搜索框 -->
<a-input style="width: 250px;margin-left: 10px;" placeholder="搜索用户名、用户编号"></a-input>
<!-- 表格 -->
<div ref="refTable" class="flex-1 px-[20px] overflow-hidden">
<a-table :pagination="pagination" scrollToFirstRowOnChange :scroll="{ y: scrollY - 40, x: 'max-content' }"
@change="pageChange" :columns="columnData"
:data-source="tableData">
</a-table>
</div>
// 分页参数
const pagination = reactive({
current: 1,
pageSize: 15,
total: 0
})
const keywords = ref('') // 搜索关键词
const tableData = ref([]) // 表格数据
const formData = ref([...customer_group_filter]); // 表单配置
// 2. computed计算请求参数, 清除对象空值
// 请求参数
const requestParams = computed(() => {
const obj = {
keywords: keywords.value
year: props.year,
current: pagination.current,
size: pagination.pageSize,
};
formData.value.forEach(({ value, key }) => {
Object.assign(obj, { [key]: value });
});
return cleanObject(obj);
});
/**
* 清理对象中的空值
* @param {T} obj
* @returns {Partial<T>}
*/
export const cleanObject = <T extends {}>(obj?: T): Partial<T> => {
if (!obj) return {};
return Object.entries(obj).reduce(
(tempObj, [key, value]) => ({
...tempObj,
...(!Object.is(value ?? '', '') && {
[key]: value,
}),
}),
{} as T,
);
};
// 3. watch监听页码和请求参数, 触发请求函数
watch([requestParams], () => {
getCustomerInfo()
}, { deep: true })
// 1. 给a-table绑定页码页数参数, change更改参数
/**
* 页码改变
*/
const pageChange = (page) => {
Object.keys(page).forEach(key => {
pagination[key] = page[key];
});
}
// 4. 请求表格数据
/**
* 请求用户数据
*/
const getCustomerInfo = async () => {
const { code, data } = await getCustomerList(requestParams.value)
if (code === 2000) {
tableData.value = data.records
}
}