前端

vue3+ts+vite初始化项目

1、使用vite构建项目

1
sudo npm create vite@latest 

可能会出现输入命令没反应的情况

1
2
3
4
npm config set registry=https://registry.npmmirror.com 

//执行以下命令查看是否配置成功
npm config get registry

2、依次命令

最后出现初始页,代表成功

1
2
3
cd NFT-Admin(将文件切换到该文件夹)
npm install (安装项目依赖)
npm run dev (运行项目)

接着记得给权限,否则无法保存任何文件

1
sudo chmod -R 777 /Users/tec/NFT-Admin

3、将项目文件夹拖入VsCode

但是会出现两个报错

第一个是找不到模组vue

1
2
import { ref } from 'vue'
// Cannot find module 'vue'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?Vetur(2792)

解决方法为修改tsconfig.json文件,然后关闭 VScode ,重新启动一下项目即可。

bundler改为node

1
2
// "moduleResolution": "bundler",
"moduleResolution": "node",

第二个是两个组件VolarVetur冲突

Vetur是针对vue2的,Volar是针对vue3的关一个就行

1
2
import HelloWorld from './components/HelloWorld.vue'
// "Module '\"/Users/tec/NFT-Platform/src/components/HelloWorld.vue\"' has no default export."

4、引入element plus

首先通过 npm 下载

1
npm install element-plus --save

其次安装两个插件实现自动导入

1
npm install -D unplugin-vue-components unplugin-auto-import

最后配置vite.config.ts文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})

5、引入vue-router

1
npm install vue-router@4

新建 router 文件夹,在该文件夹下面新建 index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";

// 2. 配置路由
const routes: Array<RouteRecordRaw> = [
{
path: "/",
component: () => import("../views/index.vue"),
},
{
path: "/hello",
component: () => import("../components/HelloWorld.vue"),
},
];
// 1.返回一个 router 实列,为函数,里面有配置项(对象) history
const router = createRouter({
history: createWebHistory(),
routes,
});

// 3导出路由 然后去 main.ts 注册 router.ts
export default router

在 main.ts 中挂载 router

1
2
3
4
5
6
7
8
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router/index"

createApp(App)
.use(router)
.mount('#app')

将程序入口 App.vue 内部改为 router入口

1
2
3
4
5
6
7
<script setup lang="ts">

</script>

<template>
<router-view></router-view>
</template>

6、一键生成vue模版

在 VsCode 界面按住 command+shift+P ,然后在上方的输入栏中输入snippets,回车后,再次输入vue,进入 vue.json 的文件中,输入下面的模版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Print to console": {
"prefix": "vue",
"body": [
"<template>",
" <div class=\"\"></div>",
"</template>\n",
"<script setup lang=\"ts\">",
"import {} from \"vue\"",
"</script>\n",
"<style lang=\"scss\" scoped></style>",
"$2"
],
"description": "Log output to console"
}
}

然后在 vue 文件中,输入 vue 则可得到下面的模版

1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="">

</div>
</template>

<script setup lang="ts">
import { } from "vue"
</script>

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

7、引入scss

在 vue3+vite 中已经内置了 scss 的相关启动器,只需要下载一个 sass 就行,比 webpack 所需要加入的依赖少得多

1
npm install -D sass

8、引入tailwindcss

见下面的tailwindcss学习部分

Gitee初始化仓库并提交

首先根据上面的步骤初始化vue项目之后

1、初始化本地git仓库以及

打开vscode的SOURCE CONTROL图标,第一次进去会会显示Initialize repository点击这个按钮,也就相当于进行了下面的步骤

1
2
git init
git add .

下面的是必要的连接远程仓库的命令行命令

1
git remote add origin 远程仓库URL

2、重命名分支

注意这个时候别提交,应该先更改分支的名字,因为大部分远程仓库主分支名为master,但是本地仓库默认为main为主分支,这时候可以使用vscode里面的Rename Branch...更改名字为master,同等效果的git命令我也放到下面了

截屏2024-02-15 18.36.40

1
git branch -m 旧分支名 新分支名

3、提交到远程仓库

这里要注意如果远程仓库本来就已经有代码时哦,要先拉取代码

1
git pull origin master

然后再进行提交,这里直接使用vscode的提交就行,当然相关命令我也放在下面,防止忘记

截屏2024-02-15 19.01.41

1
2
git commit -m "提交消息"
git push origin master

圆角带图标的输入框效果

如何实现圆角带图标的输入框

主要是去掉输入框默认样式:border: 0px;

Html:

1
2
3
4
5
6
<div class="SearchInput">
<el-icon :size="16">
<Search />
</el-icon>
<input type="text" placeholder="搜索">
</div>

css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 输入框样式 */
.SearchInput {
display: flex;
justify-content: start;
align-items: center;

background-color: #FFFFFF;
border-radius: 12px;
max-width: 500px;

padding: 12px;

input {
outline: none;
padding-left: 10px;
font-size: 16px;
width: 200px; /* 调整输入框的宽度 */
border: 0px;
}
}

element plus引入图标

首先通过npm下载图标包

1
sudo npm install @element-plus/icons-vue

接着在main.ts进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
import ElementPlus from 'element-plus'
import * as ElIcons from '@element-plus/icons-vue'


const app = createApp(App)
for (const name in ElIcons){
app.component(name,(ElIcons as any)[name])
}

app
.use(ElementPlus)

.mount('#app')

使用图标

1
<el-icon size="16" color="#000"><Search /></el-icon>

改变背景颜色动画效果

重点关注的其实是如何做到颜色加深或者变浅,参考下面的例子,关键就是调rgba色的透明值部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.MainNavbarUserLogin {

background-color: var(--accent-100);

/* 指定转化时的效果 */
transition: background-color 0.2s ease 0s;
}

.MainNavbarUserLogin:hover {
color: var(--text-200);
/* 悬停时的文本颜色 */
background-color: rgba(214, 198, 225, 0.7);
}

阴影设置

box-shadow

box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);

属性:水平偏移为0px,垂直偏移为10px,模糊半径为10px,阴影颜色为深度为0.1的黑色。

transition的解释看下面的向上移动5px的动画

1
2
3
4
5
6
7
8
9
10
.CollectionListItem {
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1); /* 调整阴影效果 */
transition: box-shadow 0.3s ease, transform 0.3s ease; /* 添加过渡效果 */

}
.CollectionListItem:hover {
box-shadow: 0 20px 20px rgba(0, 0, 0, 0.2); /* 鼠标悬停时的阴影效果 */
transform: translateY(-5px); /* 鼠标悬停时向上移动10px */
}

向上移动5px的动画

ransition: box-shadow 0.3s ease, transform 0.3s ease; /* 添加过渡效果 */

属性:box-shadowtransform 属性在0.3秒内以ease(平滑)的方式过渡。

1
2
3
4
5
6
7
8
9
10
.CollectionListItem {
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1); /* 调整阴影效果 */
transition: box-shadow 0.3s ease, transform 0.3s ease; /* 添加过渡效果 */

}
.CollectionListItem:hover {
box-shadow: 0 20px 20px rgba(0, 0, 0, 0.2); /* 鼠标悬停时的阴影效果 */
transform: translateY(-5px); /* 鼠标悬停时向上移动10px */
}

图片按照比例缩放

object-fit

object-fit 是 CSS 中用于控制替换元素(如 <img><video><object>)的尺寸和裁剪的属性。这个属性允许你定义替换元素在其容器内的尺寸和位置,以及如何调整替换元素的内容以适应这些尺寸。

object-fit 属性有以下几个可能的取值:

  1. fill: 默认值。替换元素被拉伸以填满容器,可能导致元素的宽高比发生变化。
1
2
3
img {
object-fit: fill;
}
  1. contain: 替换元素被缩放以适应容器,保持其宽高比可能在容器内留有空白
1
2
3
img {
object-fit: contain;
}
  1. cover: 替换元素被缩放以填满容器,保持其宽高比可能裁剪超出容器的部分。(最常用)
1
2
3
img {
object-fit: cover;
}
  1. none: 替换元素保持其原始尺寸,可能溢出容器。
1
2
3
img {
object-fit: none;
}
  1. scale-down: 替换元素的尺寸被缩小以适应容器,但不会超过其原始尺寸,可能留有空白。
1
2
3
img {
object-fit: scale-down;
}

使用 object-fit 属性,你可以更灵活地控制替换元素在容器内的布局和尺寸,以适应设计的需要。

示例:

1
2
3
<div class="CollectionListItemImage" style="height: 150px; width: 280px;">
<img style="height: 100%; width: 100%; border-radius: 20px 20px 0px 0px; object-fit: cover;" :src="item.imageUrl" alt="" />
</div>

Pinia使用

1、首先安装Pinia

1
2
# 需要 cd 到的项目目录下
sudo npm install pinia

2、更改main.ts文件

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 导入 Pinia
import App from '@/App.vue'

const app = createApp(App)

app
.use(createPinia()) // 启用 Pinia
.mount('#app')

3、创建stores文件夹

在该文件夹下面就可以创建相关的Store文件

按照之前做过的来说,一般是一个store一个文件的,但是我觉得很多比较重复,所以就相似的放到一起了

SelectedIndexStore.ts:

1
2
3
4
5
6
7
8
9
10
11
12
import { defineStore } from 'pinia';

export const SelectedTypeIndexStore = defineStore('SelectedTypeIndexStore', {
state: () => ({
index: 0,
}),
});
export const SelectedUserIndexStore = defineStore('SelectedUserIndexStore', {
state: () => ({
index: 0,
}),
});

CollectionStore.ts:
注意如何建立数组元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// src/stores/index.ts
import { defineStore } from 'pinia';

export const RecommendedCollectionStore = defineStore('RecommendedCollectionStore', {
state: () => ({
collections: [
{
imageUrl: '',
title: '',
price: '',
tradingVolume: '',
}
],
}),
});
export const CollectionRankingStore = defineStore('CollectionRankingStore', {
state: () => ({
collections: [
{
imageUrl: '',
title: '',
price: '',
tradingVolume: '',
}
],
}),
});
export const PopularAnimationCollectionStore = defineStore('PopularAnimationCollectionStore', {
state: () => ({
collections: [
{
imageUrl: '',
title: '',
price: '',
tradingVolume: '',
}
],
}),
});

4、使用 store 实例

主要首先从store的ts文件中引入

然后定义示例

最后根据示例属性进行使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { RecommendedCollectionStore } from '../stores/CollectionStore'
import { SelectedTypeIndexStore } from '../stores/SelectedIndexStore'
import { Collection } from '../interfaces/Collection';


// 像 useRouter 那样定义一个变量拿到实例
const RecommendedCollection = RecommendedCollectionStore()

// 初始值
const recommendedCollections: Collection[] = [
{
imageUrl: 'https://i.seadn.io/s/raw/files/6662e4fbea8ad15eb84990bc68351d57.png?auto=format&dpr=1&h=500&fr=1 1x, https://i.seadn.io/s/raw/files/6662e4fbea8ad15eb84990bc68351d57.png?auto=format&dpr=1&h=500&fr=1 2x',
title: 'Mint Genesis NFT',
price: '0.01 ETH',
tradingVolume: '68 ETH',
},
]

// 使用 setState 方法赋值
RecommendedCollection.collections = recommendedCollections

父组件向字组件传值

上面的Pinia作为全局管理也可以实现一样的效果,但是如果是比较简单的数据,则可以使用下面的方法

1
const props = defineProps<{ msg: string }>()

父组件使用:

1
<CollectionList msg="热门动画数字藏品" />

子组件使用:

1
const props = defineProps<{ msg: string }>()

点击切换CSS样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<template>
<div class="TypeNavbar">
<div class="TypeNavbarItem" @click="selectType(0)" :class="{ 'selected': TypeIndex.index === 0 }">
<p>全部</p>
</div>
<div class="TypeNavbarItem" @click="selectType(1)" :class="{ 'selected': TypeIndex.index === 1 }">
<p>动画</p>
</div>
<div class="TypeNavbarItem" @click="selectType(2)" :class="{ 'selected': TypeIndex.index === 2 }">
<p>现实</p>
</div>
<div class="TypeNavbarItem" @click="selectType(3)" :class="{ 'selected': TypeIndex.index === 3 }">
<p>科技</p>
</div>
<div class="TypeNavbarItem" @click="selectType(4)" :class="{ 'selected': TypeIndex.index === 4 }">
<p>动物</p>
</div>
</div>
</template>

<script setup lang="ts">
import { } from "vue";

import { SelectedTypeIndexStore } from '../stores/SelectedIndexStore'

const TypeIndex = SelectedTypeIndexStore()


const selectType = (index: number) => {

TypeIndex.index = index;
};



</script>

<style lang="scss" scoped>
.TypeNavbar {
display: flex;
justify-content: start;
align-items: center;
width: 100%;

gap: 50px;
margin-top: 20px;
margin-bottom: 50px;
font-size: 16px;
font-weight: bold;
}

// .TypeNavbarItem {
// padding: 0px 20px;
// }

.TypeNavbarItem.selected:hover {
color: var(--text-200);
/* 悬停时的文本颜色 */
background-color: rgba(214, 198, 225, 0.7);
}

.TypeNavbarItem.selected {
display: flex;
justify-content: space-around;
align-items: center;

padding: 0px 20px;
height: 40px;
min-width: 40px;
border: 1px solid transparent;

background-color: var(--accent-100);

backdrop-filter: blur(20px);
border-radius: 16px;
// 指定转化时的效果
transition: background-color 0.2s cubic-bezier(0.05, 0, 0.2, 1) 0s;
}
</style>

切换展示不同组件

根据index选中的值切换组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="IndexView" v-if="TypeIndex.index == 0">
<MainNavbar />

</div>
<div class="IndexView" v-if="TypeIndex.index == 2">
<MainNavbar />
<TypeNavbar />
<CollectionList msg="热门现实数字藏品" />
</div>
<div class="IndexView" v-if="TypeIndex.index == 3">
<MainNavbar />
<TypeNavbar />

<CollectionList msg="热门科技数字藏品" />

</div>
<div class="IndexView" v-if="TypeIndex.index == 4">
<MainNavbar />
<TypeNavbar />

<CollectionList msg="热门动物数字藏品" />
</div>
</template>

路由使用

路由的下载在第一部分讲过了

1、ts中使用

1
2
3
4
5
6
7
8
9
import { useRouter } from 'vue-router'

const router = useRouter()

const toIndex = () => {
router.push({
name: 'IndexView',
})
}

2、html中使用,主要是可以唤起鼠标的手部图标

1
<router-link to="/user">TEC</router-link>

使用overflow-y: Auto;导致的Bug

overflow-y: scroll;

使用overflow-y: Auto容易出现的问题是,同一个界面中,在切换字组件时,部分子组件长度超出父组件,overflow-y:会变为scroll,而部分子组件长度未超出父组件,overflow-y:会变为hidden,这样的情况下,会导致界面布局变化,比如宽度伸长缩短

解决的方法就是,在决定一定会超父组件时直接使用overflow-y: scroll;

手写上传文件框

ui部分:

截屏2024-02-21 20.55.43

Html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="CreateViewBodyLeft">
<p style="font-size: 36px; font-weight: bold;">创建NFT</p>
<p style="font-size: 20px; margin-top: 10px;">铸造项目后,您将无法更改其任何信息。</p>
<div class="CreateViewBodyLeftUpdate">
<el-icon size="40">
<Upload />
</el-icon>
<p style="font-size: 20px; font-weight: bold; margin-top: 20px;">拖拽媒体</p>
<p style="font-size: 16px; color: var(--primary-100); font-weight: bold;">浏览文件</p>
<p style="font-size: 16px;">最大尺寸: 50MB</p>
<p style="font-size: 16px;">JPG、PNG、GIF、SVG、MP4</p>
</div>
</div>

Css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.CreateViewBodyLeft {
min-width: 40%;

.CreateViewBodyLeftUpdate {
display: flex; /* 使用 Flex 布局 */
flex-direction: column; /* 主轴方向为垂直,交叉轴方向为水平 */
justify-content: center; /* 在主轴上居中对齐 */
align-items: center; /* 在交叉轴上居中对齐 */
gap: 5px; /* 设置子元素之间的间距 */

height: 80%;
width: 100%;
min-width: 80%;

max-height: 600px;
border: 1px dashed var(--text-200); /* 设置边框为虚线 */
border-radius: 20px;
margin-top: 30px;
background-color: var(--bg-200); /* 设置背景颜色,颜色使用 CSS 变量 */

transition: background-color 0.2s cubic-bezier(0.05, 0, 0.2, 1) 0s; /* 添加背景颜色过渡效果,持续0.2秒,使用贝塞尔曲线,无延迟 */
}


.CreateViewBodyLeftUpdate:hover {
border: 1px solid var(--text-200); /* 设置边框为实线*/
background-color: rgba(18, 18, 18, 0.04); /* 设置半透明背景颜色 */
}
}

功能实现部分:

Flex布局竖向排列元素

使用flex布局的flex-direction设置为column即可

1
2
3
4
5
6
7
8
.CreateViewBodyLeftUpdate {
display: flex; /* 使用 Flex 布局 */
flex-direction: column; /* 主轴方向为垂直,交叉轴方向为水平 */
justify-content: center; /* 在主轴上居中对齐 */
align-items: center; /* 在交叉轴上居中对齐 */
gap: 5px; /* 设置子元素之间的间距 */
}

鼠标滑动展示菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<div class="MainNavbarUserInfo" @mouseover="showUserMenu" @mouseleave="hideUserMenu">
<el-icon :size="20">
<User />
</el-icon>
</div>

<transition name="fade">
<div class="MainNavbarUserMenu" v-if="isUserMenuVisible" @mouseover="showUserMenu" @mouseleave="hideUserMenu">

</div>
</transition>


<style lang="scss" scoped>
/* 整个导航栏容器 */

.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
opacity: 0;
}


</style>

下拉菜单

主要是使用absolute定位,并使用z-index来使得菜单维持在界面最上方

但是目前存在缩放后位置有偏差的问题(‼️)

1
2
3
<div class="MainNavbarUserMenu" v-if="isUserMenuVisible" @mouseover="showUserMenu" @mouseleave="hideUserMenu">

</div>
1
2
3
4
5
6
7
.MainNavbarUserMenu {
position: absolute;
z-index: 9999;
top: 70px;
right: 210px;
}

卡片式轮播图

采用的是element-plus中的el-carousel来实现

type属性改为card就能实现卡片轮播,具体实现看下面的示例

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div class="IndexView" v-if="TypeIndex.index == 0">
<el-carousel :interval="4000" type="card" height="300px" >
<el-carousel-item v-for="(item, index) in recommendedCollections" :key="index" style="border-radius: 20px 20px 0px 0px;">
<img :src="item.imageUrl" alt="NFT Image" style="height: 100%; width: 100%; border-radius: 20px 20px 0px 0px; object-fit: cover;">
<h3 text="2xl" justify="center">{{ item.title }}</h3>
<p>{{ item.price }}</p>
<p>{{ item.tradingVolume }}</p>
</el-carousel-item>
</el-carousel>
<Rank/>
</div>
</template>

<style scoped>
.el-carousel__item h3 {
color: #475669;
opacity: 0.75;
line-height: 200px;
margin: 0;
text-align: center;
}

.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}

.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
}

</style>

动画效果

1、使用Vue提供的transition组件

前提是transition中存在v-if来控制组件的出现与否,而且注意v-if的组件必须就位于transition的下一个包裹代码汇总

这个transition中的过渡时间不生效一直不生效(‼️)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<transition name="fade">
<div class="MainNavbarUserMenu" v-if="isUserMenuVisible" @mouseover="showUserMenu" @mouseleave="hideUserMenu">

</div>
</transition>

<style lang="scss" scoped>
/* 整个导航栏容器 */

.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
opacity: 0;
}


</style>

2、使用Css提供的transition属性

transition 属性是 CSS 中用于设置过渡效果的属性。过渡效果可以让元素在状态改变时平滑地过渡到新状态,而不是突然地改变样式。transition 属性可以应用于元素的各种样式属性,如颜色、大小、位置等,以实现平滑的过渡效果。

transition 属性有以下语法:

1
transition: property duration timing-function delay;
  • property:指定要过渡的样式属性,可以是一个或多个属性,用逗号分隔。也可以使用关键字 all 表示所有属性。例如:width, height, color, all,background-color

  • duration:指定过渡的持续时间,以秒(s)或毫秒(ms)为单位。例如:0.5s, 1000ms

  • timing-function:指定过渡效果的时间函数,用于定义过渡过程中的速度变化。常见的有 ease(默认值,缓慢开始,然后加速)、linear(匀速)、ease-in(缓慢开始)、ease-out(缓慢结束)、ease-in-out(缓慢开始和结束)等。

  • delay(可选):指定在过渡开始之前的延迟时间,以秒(s)或毫秒(ms)为单位。例如:0.2s, 300ms

下面是一个例子,演示了如何使用 transition 属性:

1
2
3
4
5
6
7
8
9
/* 对于所有属性,持续时间为0.3秒,使用ease时间函数,延迟0.1秒 这个比较常用*/
.element {
transition: all 0.3s ease 0.1s;
}

/* 对于颜色属性,持续时间为1秒,使用linear时间函数,无延迟 */
.element2 {
transition: color 1s linear;
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
.MainNavbarUserCart {

/* 指定转化时的效果 */
transition: background-color 0.2s cubic-bezier(0.05, 0, 0.2, 1) 0s;
}

.MainNavbarUserCart:hover {
color: var(--text-200);
/* 悬停时的文本颜色 */
background-color: rgba(214, 198, 225, 0.7);
}

排行榜实现

重点关注如何实现序号正确排列的

还没好好看(‼️)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<div class="Rank">
<div v-for="(collectionGroup, index) in groupedCollections" :key="index" class="RankLeft">
<div class="RankTitle">
<p style="flex: 1;">#</p>
<p style="flex: 7;">系列</p>
<p style="flex: 4;text-align: end;">交易量</p>
</div>
<div class="RankBody">
<div v-for="(collection, innerIndex) in collectionGroup" :key="innerIndex" class="RankBodyItem">
<p style="flex: 1;">{{ collection.rank }}</p>
<div style="flex: 7;" class="RankBodyItemContent">
<div style="flex: 0.3;">
<img :src="collection.imageUrl" alt=""
style="height: 100%; width: 100%; border-radius: 20px; object-fit: cover; aspect-ratio: 1/1;">
</div>
<div style="padding-left: 20px;">
<p>{{ collection.title }}</p>
<p style="color: var(--text-200); padding-top: 10px;">地板价:{{ collection.price }}</p>
</div>
</div>
<p style="flex: 4; text-align: end;">{{ collection.tradingVolume }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';

const recommendedCollections = ref([
{
imageUrl: 'https://i.seadn.io/s/raw/files/6662e4fbea8ad15eb84990bc68351d57.png?auto=format&dpr=1&h=500&fr=1 1x, https://i.seadn.io/s/raw/files/6662e4fbea8ad15eb84990bc68351d57.png?auto=format&dpr=1&h=500&fr=1 2x',
title: 'Mint Genesis NFT',
price: '0.01 ETH',
tradingVolume: '68 ETH',
},
]);

const groupedCollections = computed(() => {
const grouped = [];
for (let i = 0; i < recommendedCollections.value.length; i += 5) {
grouped.push(recommendedCollections.value.slice(i, i + 5).map((collection, index) => ({ ...collection, rank: i + index + 1 })));
}
return grouped;
});
</script>

图片正方形实现

aspect-ratio: 1/1;

1
2
3
4
5
<div style="flex: 1;">
<img src="https://xxx.jpg" alt=""
style="height: 100%; width: 100%; border-radius: 20px; object-fit: cover; aspect-ratio: 1/1;">
</div>

由于父元素存在padding,导致UserBackground无法铺满横向解决

margin-left: -50px;

margin-right: -50px;

父组件存在padding,子组件背景颜色想铺满的话,需要加上负的margin

悬浮图片

主要是使用absolute定位,使图片悬浮在左下角

截屏2024-02-21 20.56.15

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.UserBackgroundAvatar {
display: flex;
justify-content: center;
align-items: center;
position: absolute;

/* 设置绝对定位,相对于包含它的 .UserBackground 定位 */
left: 5%;
bottom: -10%;
width: 20vh;
height: 20vh;

border-radius: 50%;
background-color: white;

box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
}

手写悬浮二级菜单

这个比较死可以直接拿来用

截屏2024-02-21 20.54.50

HTML部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div class="Condition">
<p>最近收到</p>
<!-- 根据isConditionListVisible决定class是rotate-0还是rotate-180 -->
<el-icon :size="16" @click="toggleConditionList" :class="isConditionListVisible ? 'rotate-180' : 'rotate-0'">
<ArrowDownBold />
</el-icon>

<div class="ConditionList" v-if="isConditionListVisible">
<div class="ConditionListItem">
<p>最近收到</p>
</div>
<div class="ConditionListItem">
<p>价格从高到低</p>
</div>
<div class="ConditionListItem">
<p>价格从低到高</p>
</div>
<div class="ConditionListItem">
<p>最近创建的</p>
</div>
<div class="ConditionListItem">
<p>最高销售价格</p>
</div>
<div class="ConditionListItem">
<p>最早的</p>
</div>
</div>
</div>

TypeScript部分:

1
2
3
4
5
6
7
8
9
<script setup lang="ts">
import { ref } from "vue"
// 定义变量控制是否展示ConditionList
let isConditionListVisible = ref(false);
// 定义一个函数用于控制ConditionList的显示与隐藏
const toggleConditionList = () => {
isConditionListVisible.value = !isConditionListVisible.value;
};
</script>

CSS部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
.Condition {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
position: relative;
width: 250px;
height: 50px;
background-color: #FFFFFF;
border-radius: 12px;
border: 0.5px solid var(--text-200);
padding: 12px;

.rotate-180 {
transform: rotate(180deg);
transition: 0.25s ease-out;
}

.rotate-0 {
transition: 0.25s ease-out;
}

.ConditionList {
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
position: absolute;
top: 60px;
right: 0px;
width: 250px;
background-color: #FFFFFF;
border-radius: 12px;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
padding: 10px;

.ConditionListItem {
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
height: 100%;
border-radius: 12px;
padding: 15px;
}

.ConditionListItem:hover {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.1);
}
}
}

手写朝着点击方向移动的动画效果

截屏2024-02-15 22.38.14

方法一:
该方法是纯自己想,缺点在于开局就会转动,优点在于遇到开局需要运动的需求,可以用上这个

方法二:

该方法才是通用的方法,首先是大体的html结构部分,抛弃方法一中的,每个位置上都放置一个白色选择块,再通过点击显示点击位置的白色选择块的方法

实际上应该只放置一个白色选择块,点击后通过css中的translateX,将该唯一白色选择块移动至点击位置,最后用transition控制移动动画的长度

HTML部分:

1
2
3
4
5
6
7
8
9
<div class="FilterSectionType" style="flex: 2;">
<!-- 应用selectType方法 -->
<div :class="{ 'Selected0': TypeIndex.index === 0, 'Selected1': TypeIndex.index === 1 }">
<!-- Content of the div -->
</div>
<p @click="selectType(0)" style="position: absolute; left: 15%; z-index: 9999;">热门</p>

<p @click="selectType(1)" style="position: absolute; right: 15%; z-index: 9999;">最佳</p>
</div>

TypeScript部分:

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">
import { ref } from "vue"
// 定义TypeIndex

let TypeIndex = ref(0)
// 实现selectType方法
const selectType = (index: number) => {
TypeIndex.value = index

}
</script>

CSS部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.FilterSectionType {
display: flex;
justify-content: center;
align-items: center;
position: relative;
min-width: 150px;
height: 40px;
border-radius: 10px;
background-color: var(--accent-100);

/* 定义共同的样式 */
@mixin selected-style {
position: absolute;
width: 50%;
height: 80%;
border-radius: 10px;
background-color: var(--bg-100);
transition: 0.25s ease-out;
}

.Selected0 {
@include selected-style;
transform: translateX(-45%);
}

.Selected1 {
@include selected-style;
transform: translateX(45%);
}
}

absolute布局对应偏移对象理解错误

这个其实是相当基础的问题,但是确实前端这块基础不够扎实

absolute布局对应偏移对象实际上是在相对于static定位以外的第一个父元素进行定位

其实就是他自己向上一层一层的找自己的父元素,
然后看他们的position属性,谁的position属性不是static他就以谁为标准偏移.
如果一直没有的话就会找到body,body也不是的话,但是已经是最后一层了,
所以他就只能以body的初始位置为基准了.这就是之前为什么没对齐的原因.
注:(所有的块属性的position默认为static)

后面在写前后翻页的按钮时,发现了妙用,当最外层的app具有padding时,内部的元素无法处于padding的位置上,这时候将appposition设置为relative,就能够避免padding裁掉元素

absolute布局居中对齐

参考文章:绝对定位position:absolute;实现居中对齐_position: absolute; 居中-CSDN博客

实现原理为left以及top的布局是根据元素左上角进行定位的,但是我们的需求是元素的中心点居中,那么这个时候再使用translate(-50%, -50%)将从元素左上角变换至元素中心点,达成效果

1
2
3
4
5
6
7
8
9
.main {
position: absolute;
width: 700px;
height: 500px;
background: pink;
left: 50%; /* 起始是在body中,横向距左50%的位置 */
top: 50%; /* 起始是在body中,纵向距上50%的位置,这个点相当于body的中心点,div的左上角的定位 */
transform: translate(-50%, -50%); /* 水平、垂直都居中 */
}

父子组件通讯

使用prop/emit来实现,

当使用 propemit 进行父子组件通信时,主要涉及两个概念:props 和自定义事件。

下面我将分别从父组件以及子组件介绍用法

父组件

Props(属性)

在父组件中,通过 props 可以将数据传递给子组件。在子组件中,可以通过定义 props 属性来接收这些数据。

下面的例子中props名为ifShow,实际的值来自于isLoginBoxVisible,注意是可以传递多个属性的

Emit(自定义事件)

在父组件中,Emit并不是被直接使用,而是定义Emit所需要的事件,这里的事件为updateIfShow,而对应的方法为updateIsLoginBoxVisible,这里注意事件与方法的区别,以及方法的格式是需要参数的

父组件中的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- ParentComponent.vue -->
<template>
<div>
<LoginBox :ifShow="isLoginBoxVisible" @updateIfShow="updateIsLoginBoxVisible" />
</div>
</template>

<script setup lang="ts">
// 引入LoginBox
import LoginBox from '../components/LoginBox.vue'

// isLoginBoxVisible设置默认为false
const isLoginBoxVisible = ref(false);

// hideMaskLayer方法控制更新isLoginBoxVisible
const updateIsLoginBoxVisible = (value: boolean) => {
isLoginBoxVisible.value = value;
};

const showLogin = () => {
updateIsLoginBoxVisible(true);
}
</script>

子组件

Props(属性)

在子组件中,使用defineProps方法接收父组件传进来的Props,使用方法为props.(属性名)(props.ifShow

Emit(自定义事件)

在子组件中,通过 emit 方法可以触发自定义事件,并将数据传递给父组件。使用方法:首先通过defineEmits实例化一个emit,接着需要按照emit(‘事件名’,事件的方法参数)的方式调用父组件的事件

子组件中的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- ChildComponent.vue -->
<template>
<div class="LoginBox" v-if="props.ifShow">
<!-- 子组件的其他内容 -->
</div>
</template>

<script setup lang="ts">
import { } from 'vue';

const props = defineProps(['ifShow']);
const emit = defineEmits();

const toggleVisibility = () => {
emit('updateIfShow', false);
};

</script>

手写radio

将input的type设置为radio即可,需要注意的是如何更改小圆点的颜色,网上查了很多,但是都复杂且无效,实际上只需要更改accent-color就可以更改小圆点颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="payMethod">
<p>支付方式</p>

<label>
<input type="radio" name="paymentMethod" value="alipay">
支付宝
</label>

<label>
<input type="radio" name="paymentMethod" value="wechat">
微信支付
</label>
</div>

鼠标划动事件

不是hover,而是@mouseover="handleHover"

1
<div class="Detail" v-if="!isCartNullVisible" @mouseover="handleHover"></div>

ref问题

使用ref时注意要用.value

1
2
3
4
5
6
// 定义一个变量isDeleteVisible
let isDeleteVisible = ref(false);
// 实现showDelete方法
const showDelete = () => {
isDeleteVisible.value = true;
}

悬停于v-for形成的元素时,仅其中一个元素,而不是全部都改变

创建一个对应cartList的数组来实现,首先是通过map创建了一个全是false的数组(序号与cartList对应),该数组内的值会在鼠标移入时触发mouseover事件,将该值改为true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div v-for="(item, index) in cartList" :key="index" class="DetailBelow" @mouseover="showDelete(index)" @mouseleave="hideDelete()">
<div style="flex: 1;">
<p v-if="isDeleteVisible[index] && !isDeleteVisible[index].value">{{ item.price }}</p>
<div v-else @click="deleteCart(index)">
<el-icon>
<Delete />
</el-icon>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// 定义一个变量isDeleteVisible
let isDeleteVisible = cartList.value.map(() => ref(false));
//当cartList改变时,isDeleteVisible也重新赋值
watch(cartList.value, (newValue, oldValue) => {
isDeleteVisible = newValue.map(() => ref(false));
console.log('watch 已触发', oldValue)
})
// 实现showDelete方法
const showDelete = (index: number) => {
isDeleteVisible.forEach((item, i) => (item.value = i === index));
};
// 实现hideDelete方法
const hideDelete = () => {
isDeleteVisible.forEach((item) => (item.value = false));
};
</script>

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading ‘value’)(‼️)

位于CartList的错误,触发条件为商品添加进全局状态CartListCollection

错误报错位于

1
<p v-if="!isDeleteVisible[index].value">{{ item.price }}</p>

补充代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一个数组用于储存购物车的内容,并且为响应式
// TODO:记得cartList一定是要放在全局变量里面的,否则清除所有就是清除不了的
const cartList = ref<Collection[]>([])

// cartList赋值给CartListCollection
cartList.value=CartListCollection.collections;

// 定义一个变量isDeleteVisible
let isDeleteVisible = cartList.value.map(() => ref(false));
// 实现showDelete方法
const showDelete = (index: number) => {
isDeleteVisible.forEach((item, i) => (item.value = i === index));
};
// 实现hideDelete方法
const hideDelete = () => {
isDeleteVisible.forEach((item) => (item.value = false));
};

初步想带到的解决方法为去掉.value

1
<p v-if="!isDeleteVisible[index]">{{ item.price }}</p>

离奇的点在于isDeleteVisible确实是ref属性啊,以及在把商品添加进去之前是可以的,但是添加之后就报错

接着想到的方法为加上判断

1
<p v-if="isDeleteVisible[index] && !isDeleteVisible[index].value">{{ item.price }}</p>

但是只是不报错,它原本的功能没了

最后发现其实是数组越界,把商品添加进去后,cartList更新了,但是没有更新isDeleteVisible数组,导致同样的indexisDeleteVisible数组会超出范围

正确的方法为增加watch,在cartList更新时一同更新isDeleteVisible数组,避免数组越界:

1
2
3
4
5
6
7
8
const cartList = ref<Collection[]>([])

cartList.value = CartListCollection.collections;

watch(cartList.value, (newValue, oldValue) => {
isDeleteVisible = newValue.map(() => ref(false));
console.log('watch 已触发', oldValue)
})

watch监听失效

失效的原因是在监听ref对象时,没有加上.value

1
2
3
4
5
6
7
8
const cartList = ref<Collection[]>([])

cartList.value = CartListCollection.collections;

watch(cartList.value, (newValue, oldValue) => {
isDeleteVisible = newValue.map(() => ref(false));
console.log('watch 已触发', oldValue)
})

实现展开隐藏一段文字的效果

截屏2024-02-21 20.57.15

截屏2024-02-21 20.57.37

通过用不同的样式,实现该效果:

展开之前:

text-overflow: ellipsis: 用于在文本溢出时显示省略号(…)

white-space: nowrap:用于防止文本换行

width: 75vh;限定长度

展开之后:

不需要任何样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 展开之前 -->
<div class="descriptionDetail" v-if="!isExpanded">
<p style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 75vh;">
一段很长的文字....
</p>
<!-- 展开放在div内部实现跟在文字后面的效果 -->
<p @click="toggleExpand"><a href="#" style="min-width: 10vh; font-weight: bold;">展开</a></p>
</div>


<!-- 展开之后 -->
<div class="descriptionDetail" v-else>
<p>
一段很长的文字....
</p>
</div>
<!-- 展开放在div内部实现位于文字下一行的效果 -->
<p @click="toggleExpand" v-if="isExpanded"><a href="#" style="min-width: 10vh; font-weight: bold;">收起</a></p>

箭头反转的动画效果

关键在于记忆rotate这个旋转度数的transform效果,以及作为transition的例子

HTML部分:

点击<el-icon>元素时,通过toggleTypeList方法来切换isTypeListVisible的值,从而改变<el-icon>元素的旋转效果。

1
2
3
<el-icon :size="16" @click="toggleTypeList" :class="isTypeListVisible ? 'rotate-180' : 'rotate-0'">
<ArrowDownBold />
</el-icon>

CSS部分:

.rotate-180类将元素旋转180度,并在0.25秒内以ease-out的过渡效果完成

1
2
3
4
5
6
7
8
.rotate-180 {
transform: rotate(180deg);
transition: 0.25s ease-out;
}

.rotate-0 {
transition: 0.25s ease-out;
}

手写checkBox

目前还不能实现改变check的颜色之类的样式,后续再来补‼️

HTML部分:

1
2
3
4
5
6
<div class="TypeListItem">
<label>
<input type="checkbox" name="type" value="all" checked>
<span style="margin-left: 20px;">现实</span>
</label>
</div>

CSS部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.TypeListItem {
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
height: 100%;
border-radius: 12px;
padding: 15px;
}

.TypeListItem:hover {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.1);
}

/* 勾选框变大,勾选背景颜色为var(--bg-100),勾选时颜色为var(--bg-200) */
input[type="checkbox"] {
transform: scale(1.5);
}

tailwindcss学习

安装

官网完全缺少了vite.config的步骤,还多余了一些步骤‼️

原文链接:vite+vue3使用tailwindcss - 掘金 (juejin.cn)(原文的vite.config配置是有问题的)

1.利用npm安装tailwindcss

安装 Tailwind 以及其它依赖项:

1
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

2.创建tailwindcss的配置文件

1
npx tailwindcss init

这将会在您的项目根目录创建一个最小化的 tailwind.config.js 文件:

1
2
3
4
5
6
7
8
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [],
theme: {
extend: {}
},
plugins: []
}

3.在入口中引入tailwind

1
import "tailwindcss/tailwind.css"

4.配置tailwind.config.js文件

1
2
3
4
5
6
7
8
9
10
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

tailwind.config.js 文件中,配置 content 选项指定所有的 pages 和 components ,使得 Tailwind 可以在生产构建中,对未使用的样式进行tree-shaking

5.配置vite.config选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//注意引入
import tailwindcss from 'tailwindcss'
import autoprefixer from 'autoprefixer'

// https://vitejs.dev/config/
export default defineConfig({
/*plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),

],*/

//下面是新加入的内容
css: {
postcss: {
plugins: [tailwindcss, autoprefixer]
}
}
})

使用postcsstailwindcssautoprefixer插件对,css进行处理

6.配置vscode的代码提示

这个步骤vscode配过一次就行,其实配置到这里我已经完成对tailwind的安装,但在模板中仍没有智能的提示,此时需要去settings.json中,在末尾添加以下代码段:

1
2
3
"editor.quickSuggestions": {
"strings": true
}

基本属性

  1. 布局

- w: width

- max-w: max-width

- h: height

- max-h: max-height

- m: margin

- mt: margin-top

- mb: margin-bottom

- ml: margin-left

- mr: margin-right

- p: padding

- pt: padding-top

- pb: padding-bottom

- pl: padding-left

- pr: padding-right

  1. 文本样式

- font: font-family

- text: text-color, text-alignment, text-transform, font-size

- leading:line-height

- tracking: letter-spacing

- uppercase: text-transform: uppercase

- lowercase: text-transform: lowercase

  1. 背景和边框

- bg: background-color

- border: border-style, border-width, border-color

- rounded: border-radius

- shadow: box-shadow

  1. 弹性盒子布局

- flex: display: flex

- justify: justify-content

- items: align-items

- self: align-self

- order: order

- flex-grow: flex-grow

- flex-shrink: flex-shrink

  1. 网格布局

- grid-cols: grid-template-columns

- grid-rows: grid-template-rows

- gap: grid-gap

  1. 响应式设计

- sm, md, lg, xl: 分别对应移动设备、平板、桌面、大屏幕

- hover: 鼠标悬停时的样式

- focus: 元素获取焦点时的样式

除了上面列举的 Tailwind CSS 缩写和对应含义之外,Tailwind CSS 还提供了很多其他的实用程序类,以下是一些常用的 Tailwind CSS 缩写和对应含义:

  1. 边框和分隔符

- divide: 分隔符 (border-color, border-style, border-width)

- divide-x: 水平分隔符 (border-color, border-style, border-width)

- divide-y: 垂直分隔符 (border-color, border-style, border-width)

- border-collapse: 设置边框是否合并

  1. Flexbox 尺寸和排列

- flex-wrap: 等同于 flex-flow 中的 wrap

- flex-row, flex-row-reverse, flex-col, flex-col-reverse: flex-direction 的简写

- flex-1…flex-12: 设置 flex-grow、flex-shrink 和 flex-basis 属性

- gap-x: 水平包裹在对象(如 flex 子元素)之间的间距。

- gap-y: 垂直包裹在对象(如 flex 子元素)之间的间距。

- space-x: 水平排列中对象(如 flex 子元素)之间的空间

- space-y: 垂直排列中对象(如 flex 子元素)之间的空间

  1. Z-index

- z-{n}: 设置 z-index 的值,其中 n 为正整数

  1. 动画

- animate-{name}: 向元素添加动画(使用 @keyframes 中定义的动画名称)

  1. 列表样式

- list-style-{type}: 设置列表项的类型 (disc, decimal, decimal-leading-zero)

  1. 转换和过渡

- transform: 让元素旋转、缩放、倾斜、平移等

- transition-{property}: 用于添加一个过度效果 {property} 的值是必需的。

  1. 颜色

- text-{color}: 设置文本颜色

- bg-{color}: 设置背景颜色

- border-{color}: 设置边框颜色

  1. 字体权重

- font-thin: 字体细

- font-light: 字体轻

- font-normal: 字体正常

- font-medium: 字体中等

- font-semibold: 字体半粗

- font-bold: 字体粗

- font-extrabold: 字体特粗

- font-black: 字体黑

  1. SVG

- fill-{color}: 设置 SVG 填充颜色

- stroke-{color}: 设置 SVG 描边颜色

  1. 显示和隐藏

- hidden: 隐藏元素(display: none)

- invisible: 隐藏元素,但仍保留该元素的布局和尺寸

- visible: 显示元素

  1. 清除浮动

- clear-{direction}: 清除某个方向的浮动效果

  1. 容器

- container: 将内容限制在最大宽度的容器内部

- mx-auto: 实现水平居中(margin-left 和 margin-right 设置为 auto)

以上是一些常用的 Tailwind CSS 缩写及其对应的意义,覆盖了基础的布局、文本、背景、边框、弹性盒子布局、网格布局和响应式设计,有助于更快速地开发出具有良好用户体验的 Web 应用程序。

常用属性

flex布局

1
2
3
<div class="flex justify-center items-center flex-col gap-2">

</div>

字体大小

依次加减2px

1
2
3
<p class="text-xs text-sm text-base text-lg text-xl">

</p>

字重

从font-normal开始依次加减100

1
2
3
<p class="font-light font-normal font-medium font-semibold font-bold font-extrabold">

</p>

字位置偏向

1
2
3
<p class="text-left">

</p>

字颜色:

常用灰色

1
2
3
<p class="text-neutral-500">

</p>

任意值

1
2
3
<p class="text-[#50d71e]">
<!-- ... -->
</p>

例子:

灰色的圆形div:

前面的flex布局时为了,内部的元素水平垂直居中

1
2
3
<div class="flex justify-center items-start p-3 bg-neutral-300 rounded-full">
<el-icon><ChatLineSquare /></el-icon>
</div>

圆形且缩放正常的图片:

1
<img class="w-9 h-9 rounded-full object-cover aspect-square" src="../assets/images/Avatar1@2x.png" alt="">

优化写法

使用@layer引入自定义样式

1
2
3
4
5
6
7
8
9
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
.content-auto {
content-visibility: auto;
}
}

在class中使用自定义样式

1
2
3
<div class="lg:dark:content-auto">
<!-- ... -->
</div>

CSS基础单位复盘

px

最基础的单位,通常用于有设计稿的情况下,或者不需要进行不同的适配

rem

根据字体的单位进行改变,和px兑换的比例为1:4

%

具体可看下面的auto与100%的对比

vh

根据视图高度进行改变

vw

根据视图宽度进行改变

媒体查询

注意max-width为在窗口宽度小于等于 1300px 时,min-width为在窗口宽度大于等于 1300px 时,启用样式

1
2
3
4
5
6
7
/* 在窗口宽度小于等于 1300px 时,调整最小宽度 */
@media (max-width: 1300px) {
.CollectionListItem {
min-width: 155px;
max-width: 255px;
}
}

点击翻页功能

主要是通过更新数组来实现(还没好好看‼️)

这里本来是做一个滑动的动画效果,但是实在是没太多思路,有的思路也到处有问题,于是就换成更新数组来实现了

HTML部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<div class="CollectionListAll">
<div class="PageBefore" @click="goToPreviousPage">
<el-icon>
<ArrowLeftBold />
</el-icon>
</div>
<!-- 使用translateX实现翻页效果 style="transform:translateX(-280px)" -->
<div class="CollectionListItems">
<div v-for="(item, index) in displayedItems" :key="index" class="CollectionListItem" @click="toNft">
<div class="CollectionListItemImage" style="height: 150px; width: 100%;">
<img style="height: 100%; width: 100%; border-radius: 20px 20px 0px 0px; object-fit: cover;"
:src="item.imageUrl" alt="" />
</div>

<p style="text-align: left; padding: 10px 20px;">{{ item.title }}</p>
<div class="CollectionListItemDetail">
<div>
<p class="text-base font-normal">交易价格</p>
<p>{{ item.price }}</p>
</div>
<div>
<p class="text-base font-normal">24小时交易量</p>
<p>{{ item.tradingVolume }}</p>
</div>
</div>
</div>
</div>
<!-- TODO:Page应该以fix或者absulute的布局放在外层,目前的布局会与overflow:hidden冲突 -->
<div class="PageNext" @click="goToNextPage">
<el-icon>
<ArrowRightBold />
</el-icon>
</div>
</div>

TypeScript部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue"

const currentPage = ref(0);
const displayedItems = ref<Collection[]>([]);

const updateDisplayedItems = () => {
const itemsPerPage = calculateItemsPerPage();
const startIndex = currentPage.value * itemsPerPage;
displayedItems.value = collectionItems.slice(startIndex, startIndex + itemsPerPage);
};

const calculateItemsPerPage = () => {
const screenWidth = window.innerWidth;

if (screenWidth < 912) {
return 2;
} else if (screenWidth < 1235) {
return 3;
} else if (screenWidth < 1520) {
return 4;
} else if (screenWidth < 1809) {
return 5;
} else {
return 6;
}
};

const goToPreviousPage = () => {
if (currentPage.value > 0) {
currentPage.value--;
updateDisplayedItems();
}
};

const goToNextPage = () => {
const totalPages = Math.ceil(collectionItems.length / calculateItemsPerPage());
if (currentPage.value < totalPages - 1) {
currentPage.value++;
updateDisplayedItems();
}
};

onMounted(() => {
updateDisplayedItems();
window.addEventListener('resize', updateDisplayedItems);
});

onBeforeUnmount(() => {
window.removeEventListener('resize', updateDisplayedItems);
});

</script>

Sass学习

主要是学习了@mixin结合@include的用法,抽离出css进行封装,多次使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义共同的样式
@mixin selected-style {
position: absolute;
width: 50%;
height: 80%;
border-radius: 10px;
background-color: var(--bg-100);
transition: 0.25s ease-out;
}

.Selected0 {
@include selected-style;
transform: translateX(-45%);
}

.Selected1 {
@include selected-style;
transform: translateX(45%);
}

el-scrollbar

用于替换浏览器原生滚动条。

比起原生滚动条的优点是,在鼠标移入时才会展示滚动条,以及选定滚动的区域更简单

注意只要当元素高度超过最大高度,才会起作用

1
2
3
4
5
<template>
<el-scrollbar height="400px">
<p v-for="item in 20" :key="item" class="scrollbar-demo-item">{{ item }}</p>
</el-scrollbar>
</template>

!important

!important是提高样式规则优先级的声明。当你在样式规则中使用!important时,它会覆盖其他相同样式规则中的属性,并强制应用该规则,即使它的优先级较低。

1
2
3
p {
color: red !important;
}

动态引入图标

vue3.0+新版elementPlus中如何动态插入icon图标的问题_vue3.0 添加图标-CSDN博客

注意is中的内容如下:icon: ‘GoodsFilled’(用图表的名字即可)

1
2
3
<el-icon>
<component :is="menu.icon"></component>
</el-icon>

多个router-view使用(⚠️)

放到子组件里就行

默认进入某一页面,把children路由path设置与父路由相同就行

1
2
3
4
5
6
7
8
9
10
11
12
const routes: Array<RouteRecordRaw> = [
{
path: "/",
component: () => import("../views/IndexView.vue"),
children: [
{
path: "/",
component: () => import("../views/HomeView.vue"),
},
],
},
];

手写侧栏(⚠️)

通过ul以及li来展示菜单项

同时把菜单项放入了数组menu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<template>
<div class="Sidebar">
<div class="sidebar-logo-container">
<img class="h-8" src="https://yiming_chang.gitee.io/vue-pure-admin/logo.svg">
<p>HyperStarAdmin</p>
</div>
<el-scrollbar height="90%">

<ul>
<!-- 遍历菜单项 -->
<li v-for="(menu, index) in menus" :key="index">
<div class="menu-item" @click="selectMenu(index, menu.children, menu.path!)"
:class="{ 'active-menu': selectedMenu === index }">
<el-icon color="#3E8CFF" v-if="selectedMenu === index">
<component :is="menu.icon"></component>
</el-icon>
<el-icon v-else>
<component :is="menu.icon"></component>
</el-icon>
<p>{{ menu.label }}</p>
<!-- 如果有子菜单,显示箭头 -->
<el-icon v-if="menu.children" class="ml-7">
<ArrowDownBold v-if="!ifShowSubMenu" />
<ArrowUpBold v-else />
</el-icon>

</div>
<!-- 如果有子菜单,渲染子菜单 -->
<ul v-if="menu.children && ifShowSubMenu">
<li v-for="(child, childIndex) in menu.children" :key="childIndex">
<div class="menu-item child-menu"
@click="selectSubMenu(index, childIndex, menu.children[childIndex].path!)"
:class="{ 'active-menu': selectedSubMenu === childIndex }">
<p class="ml-6">{{ child.label }}</p>
</div>
</li>
</ul>
</li>
</ul>


</el-scrollbar>

</div>
</template>

<script setup lang="ts">
// const handleOpen = (key: string, keyPath: string[]) => {
// console.log(key, keyPath)
// }
// const handleClose = (key: string, keyPath: string[]) => {
// console.log(key, keyPath)
// }

import { ref } from 'vue';
import { useRouter } from 'vue-router';
// 实例化router
const router = useRouter();

const selectedMenu = ref<number | null>(null);
const selectedSubMenu = ref<number | null>(null);
// ifShowSubMenu
const ifShowSubMenu = ref<boolean>(false);

const menus = [
{ label: '首页', icon: 'HomeFilled', path: '/' },
{ label: '数字藏品管理', icon: 'GoodsFilled', path: '/goods' },
{
label: '用户管理',
icon: 'Menu',
path: '/account',
},
{
label: '交易管理',
icon: 'List',
path: '/order',
children: [
{ label: '订单处理', path: '/audit' },
{ label: '营销与推广', path: '/marketing' },
],
},

{ label: '数据', icon: 'TrendCharts', path: '/data' },
{ label: '设置', icon: 'Setting', path: '/setting' },
];
// 保证第一个选择的是首页
selectedMenu.value = 0;


const toggleSubMenu = () => {
// 翻转子菜单的显示状态
ifShowSubMenu.value = !ifShowSubMenu.value;
if (ifShowSubMenu.value) {
selectedSubMenu.value = 0; // 清除子菜单的选中状态
}
};


const selectMenu = (index: number, ifChildren: any, path: string) => {
if (!ifChildren) {
selectedMenu.value = index;
selectedSubMenu.value = null; // 清除子菜单的选中状态
router.push(path)

} else {
selectedMenu.value = null;
selectedSubMenu.value = 0; // 清除子菜单的选中状态
router.push(menus[index].children![0].path!);
toggleSubMenu();

}

};

const selectSubMenu = (parentIndex: number, childIndex: number, path: string) => {
router.push(path)

selectedMenu.value = null;
selectedSubMenu.value = childIndex;
};



</script>

<style lang="scss" scoped>
.Sidebar {
height: 100%;
border-radius: 24px;
background: #fff;
.sidebar-logo-container {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;

padding: 20px 0;

p {
font-weight: 800;
color: var(--text-100);
font-size: 18px;

}
}

.menu-item {
display: flex;
align-items: center;
gap: 12px;


padding: 15px 20px;
margin: 10px;
cursor: pointer;
transition: all 0.3s ease; /* 添加过渡效果 */

&:hover {
border-radius: 20px;
background: var(--primary-100);
}


p {
font-size: 16px;
color: var(--text-100);
}

&.active-menu {
border-radius: 20px;
background: var(--primary-100);


}
&.child-menu {
// 定义子菜单的样式
&.active-menu {
background: var(--primary-100);

}
}
}
}
</style>

手写表格table(⚠️)

el-table(⚠️)

基础使用

1
2
3
<el-table :data="tableData" stripe class="tableBox" table-layout="fixed" @selection-change="handleSelectionChange">
<el-table-column prop="itemName" label="藏品名称"></el-table-column>
</el-table>

数据设置

排序按钮

插入自定义内容

行高

原文参考:el-table-column设置高度/指定高度-CSDN博客

:row-style=”{ height: ‘100px’ }”

el-pagination中文实现

原文链接:el-pagination 分页组件 ‘英文’ 修改为 ‘中文’(Vue3+ElementPlus实现) - 掘金 (juejin.cn)

vue2版好像直接就是中文

vue3版需要在外面套一层el-config-provider标签进行翻译,并在script中引入中文包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<el-config-provider :locale="zhCn">
<el-pagination
class="h-46 mr-50 flex justify-end"
v-if="pagination.isShow && pagination.total > 0"
v-model="pagination.current"
:layout="pagination.layout"
:pager-count="5"
:page-sizes="pagination.pageSizes"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-config-provider>
<template/>
<script setup>
// ElConfigProvider 组件
import { ElConfigProvider } from 'element-plus';
// 引入中文包
import zhCn from 'element-plus/es/locale/lang/zh-cn';
// 更改分页文字
// zhCn.el.pagination.total = '共 `{total} 条`';
// zhCn.el.pagination.goto = '跳至';
// zhCn.el.pagination.pagesize = '条/页';
// zhCn.el.pagination.pageClassifier = '页';
<script/>

el-button新版变化

主要是改变颜色的方式不一样了,现在使用type改变颜色,之前是用type改变按钮的类型

text表示是文字按钮

bg为增加按钮灰色背景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<el-button
text bg type="primary"
size="small"
>
上架
</el-button>

<el-button
type="text" size="small" class="blueBug"
size="small"
>
下架
</el-button>

插槽学习

原文参考:Vue3中slot插槽的使用 详细!! - 掘金 (juejin.cn)

基本概念:

使得组件中可以被插入内容,如同HTML 标签之间是可以插入内容的

虽然 child 不是 HTML 自带的标签,插槽可以使其内部被插入但是它却有着类似的特征,比如我们往<child></child>之间插入一点内容

基础使用:

子组件:

1
2
3
4
5
6
7
<template>
<div class="child-box">
<p>我是子组件</p>
<!-- 插槽 -->
<slot></slot>
</div>
</template>

父组件App.vue

1
2
3
4
5
<template>
<child>
<div>小猪课堂</div>
</child>
</template>

效果如下:

截屏2024-02-21 14.45.16

进阶使用:

1、插槽默认内容:

slot中加入内容就行,这样会在父组件使用<child></child>不加入更多内容时默认出现,<child><div>小猪课堂</div></child>时则会消失

子组件:

1
2
3
4
5
6
7
8
9
<template>
<div class="child-box">
<p>我是子组件</p>
<!-- 插槽 -->
<slot>
<p>我是默认内容</p>
</slot>
</div>
</template>

2、具名插槽

当子组件中使用多处插槽时,就会需要使用到具名插槽

子组件:

注意外层这个header组件,根据我的实验来说是不需要的,不过官网的例子是加上了的,但是估计是可以简写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="child-box">
<p>我是子组件</p>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>

3、动态插槽名

外层加一个中括号[ ]就行

1
2
3
4
5
<base-layout>
<template #[dynamicSlotName]>
...
</template>
</base-layout>

父组件:

注意外层必须嵌套一个template,带上名字,v-slot:header可以使用#header来简写

1
2
3
4
5
6
7
8
9
10
11
<template>
<child>
<template v-slot:header>
<div>我是 header:{{ message }}</div>
</template>
<div>我没有名字:{{ message }}</div>
<template v-slot:footer>
<div>我是 footer:{{ message }}</div>
</template>
</child>
</template>

4、默认插槽作用域传值

用于满足需要在插槽内容中获取子组件数据的需求

子组件:

1
2
3
4
5
6
<template>
<div class="child-box">
<p>我是子组件</p>
<slot text="我是子组件小猪课堂" :count="1"></slot>
</div>
</template>

父组件:

在父组件 App.vue 中通过 v-slot="slotProps"等形式接收子组件传毒过来的数据,slotProps 的名字是可以任意取的,它是一个对象,包含了所有传递过来的数据。

1
2
3
4
5
<template>
<child v-slot="slotProps">
<div>{{ slotProps.text }}---{{ slotProps.count }}</div>
</child>
</template>

5、具名插槽作用域传值

具名插槽作用域之间的传递其实默认插槽作用域传值原理是一样的,只不过写法略微不一样罢了

子组件:

1
2
3
4
5
6
<template>
<div class="child-box">
<p>我是子组件</p>
<slot name="header" text="我是子组件小猪课堂" :count="1"></slot>
</div>
</template>

父组件:

1
2
3
4
5
6
7
<template>
<child>
<template #header="{ text, count }">
<div>{{ text }}---{{ count }}</div>
</template>
</child>
</template>

el-image组件自定义加载失败内容无效的BUG

问题描述:

下面的有关自定义加载失败内容代码,跟官网一样,但是却无法正确显示自定义的加载失败内容

解决关键:

注意复制的imageUrl的URL本身是否就带有加载失败URL

解决过程:

首先本来使用的是vue2的版本,先去官网找到最新的使用方法

但是还是无法显示,于是接着看,是不是真的与官网一致,于是发现官网的el-image什么属性都没有加,于是我这样照做后,发现自定义加载失败内容出来了

接着就是分别去掉我的属性,看看究竟是什么属性导致的错误

最后发现是来自于src里面的imageUrl由于是直接复制别的网站的URL,该网站自己又有自定义加载失败图片,所以我自己的自定义没有成功,因为实际上图片并没有加载失败

1
2
3
4
5
6
7
8
9
10
11
<el-table-column prop="imageUrl" label="图片">
<template v-slot="{ row }">
<el-image style="width: auto; height: 40px; border: none; cursor: pointer;" :src="row.imageUrl">
<template #error>
<div class="image-slot">
<img src="../assets/images/no-image.png" style="width: auto; height: 40px; border: none;">
</div>
</template>
</el-image>
</template>
</el-table-column>

width属性100%和auto的区别

参考文章:
width:100%和width:auto的区别_width:100%-CSDN博客

主要区别:

width:100%:子元素的宽度和父元素的宽度相等,其中并不包括子元素内外边距以及边框的值,为子元素真正的宽度

width:auto:auto表示子元素的 宽度+内边距+外边距+边框 才等于父元素的宽度

点击全屏功能

这个功能比较死,可以直接copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<el-icon size="20" @click="toggleFullScreen" style="cursor: pointer;">
<FullScreen />
</el-icon>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const isFullScreen = ref(false);

const toggleFullScreen = () => {
if (isFullScreen.value) {
exitFullScreen();
} else {
enterFullScreen();
}
};

const enterFullScreen = () => {
const element = document.documentElement;

if (element.requestFullscreen) {
element.requestFullscreen();
}

isFullScreen.value = true;
};

const exitFullScreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
}

isFullScreen.value = false;
};

watch(isFullScreen, (newValue) => {
// 在这里可以处理全屏状态变化后的逻辑
console.log('全屏状态变化:', newValue);
});
</script>

el-image预览图片穿透的BUG

参考文章:el-image在el-table中使用时层级问题 - 掘金 (juejin.cn)

问题描述

截屏2024-02-21 19.30.08

解决关键:设置preview-teleported为true

1
2
3
4
5
6
7
<el-image
style="height: 60px; width: 60px; border-radius: 12px; object-fit: cover; aspect-ratio: 1/1;"
:src="row.imageUrl"
:preview-src-list="[row.imageUrl]"
:preview-teleported="true"
>
</el-image>

解决过程:首先是怀疑自己的z-index写多了,然后改掉了几个,问题没解决,然后发现el-image有一个index属性,将其调到无敌高,还是没有解决,最后网上搜下,解决问题

el-switch学习

需要注意v-model必须是Boolean类型

size控制大小,参数有large、small、默认(不需要写size属性)

inline-prompt,控制文本是否显示在点内

inactive-iconactive-icon 属性,来添加关闭以及开启时的图标。

1
2
3
4
5
6
7
<el-switch
v-model="row.availability"
size="large"
inline-prompt
:active-icon="Check"
:inactive-icon="Close"
/>

el-check样式改变(⚠️)

注意要使用:deep(/deep/会报错)

1
2
3
4
:deep .el-checkbox__inner {
border-radius: 50%;
zoom: 150%;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
:deep .el-checkbox__inner {
width: 19px;
height: 19px;
border-radius: 22.5px;
}

:deep .el-checkbox__inner::after {
border: 1px solid #fff;
border-left: 0;
border-top: 0;
left: 7px;
top: 4px;
}

:deep .el-checkbox__input.is-checked .el-checkbox__inner::after {
transform: rotate(50deg) scaleY(1.5);
}

el-progress学习

注意percentage必须是number类型

status属性控制颜色:success(绿)、warning(黄)、exception(红)

stroke-width 属性更改进度条的高度

text-inside 属性,通过布尔值,来改变进度条内部的文字

type 属性来指定使用环形进度条:circle

1
2
3
4
5
6
<el-progress
:status="row.status"
:text-inside="true"
:stroke-width="18"
:percentage="row.salesPercentage"
/>

ECharts(⚠️)

引入

首先下载包

1
npm install echarts --save

然后在想要使用的位置,引入包(这里属于局部引入,个人觉得比全部引入好些)

1
import * as echarts from 'echarts';

基础使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template>
<div ref="chartContainer" style="width: 600px; height: 400px;"></div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';

const chartContainer = ref<HTMLElement | null>(null);
let myChart: echarts.ECharts | null = null;

onMounted(() => {
// 在组件挂载后初始化 ECharts 实例
myChart = echarts.init(chartContainer.value!);

// 调用渲染图表的方法
renderChart();
});

// 在这里编写渲染图表的方法
const renderChart = () => {
// ECharts 配置项
const options: echarts.EChartOption = {
// 在这里配置你的柱状图数据和其他选项
xAxis: {
type: 'category',
data: ['Category 1', 'Category 2', 'Category 3'],
},
yAxis: {
type: 'value',
},
series: [
{
name: 'Series 1',
type: 'bar',
data: [30, 40, 20],
},
],
};

// 使用 setOption 方法设置图表配置
myChart?.setOption(options);
};
</script>

基础样式

柱状图:

改变y轴位置(默认左侧)

1
2
3
4
5
6
7
8
// ECharts 配置项
const options = {
yAxis: {
type: 'value',
position: 'right', // 将 Y 轴移到右侧显示
},
};

桩状增加圆角

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ECharts 配置项
const options = {
series: [
{
itemStyle: {
normal: {
// 这里设置柱形图圆角 [左上角,右上角,右下角,左下角]
barBorderRadius: [8, 8, 0, 0]
}
}
},
],
};

桩状改变颜色

1
2
3
4
5
6
7
8
9
// ECharts 配置项
const options = {
series: [
{
color: ['#5DB1FF'],
},
],
};

颠倒柱状图,x轴变为y轴,y轴变为x轴

调换一下两个轴的type就行

1
2
3
4
5
6
7
8
9
// ECharts 配置项
const options = {
xAxis: {
type: 'value',
},
yAxis: {
type: 'category',
},
};

默认柱状图或折线图过小的问题

参考文章:echarts图表的大小调整的解决方案 - 简书 (jianshu.com)

参数具体含义如图所示:

1
2
3
4
5
6
7
8
9
10
11
// ECharts 配置项
const options = {
grid: {
x: 0,
y: 10,
x2: 0,
y2: 20,
borderWidth: 1,
},
};

折线图:

折线下渐变色,以及弧度大小,以及折线颜色

原文参考:echarts 折线图小圆点修改为实心,折线图下方半透明效果_echarts折线图实心点-CSDN博客

效果参考:

![截屏2024-02-23 22.01.35](/Users/tec/Library/Application Support/typora-user-images/截屏2024-02-23 22.01.35.png)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ECharts 配置项
const options = {
series: [
{
color: ['#5DB1FF'],
areaStyle: {
color: {
type: 'linear', //折线颜色
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(93, 177, 255, 0.2)' }, // 开始颜色
{ offset: 1, color: 'rgba(93, 177, 255, 0)' }, // 结束颜色
],
},
},
smooth: 0.5,
},
],
};

折线上小圆点大小,以及是否实现

1
2
3
4
5
6
7
8
9
10
11
// ECharts 配置项
const options = {
series: [
{
type: 'line',
symbol: 'circle', //将小圆点改成实心 不写symbol默认空心
symbolSize: 6, //小圆点的大小
},
],
};

饼图:

集大成的效果参考:

截屏2024-02-23 22.38.07控制图例的位置,摆放方向等各类属性

原文参考:echarts 饼图以及图例的位置及大小,环图中间字_echarts饼状图文字位置-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
// ECharts 配置项
const options = {
// 配置选项
legend: {
data: ['男', '女', '未知'], // 添加图例名称
orient: 'vertical', // 控制图例是竖着放还是横着放
left: '70%', // 图例距离左的距离
y: 'center', // 图例上下居中
itemGap: 30, // 控制每一项的间距,也就是图例之间的距离
itemHeight: 15, // 控制图例图形的高度
},
};

控制饼图本身的位置大小等各类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ECharts 配置项
const options = {
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'], // 内圈以及外圈的大小
center: ['30%', '50%'], // 图的位置,距离左跟上的位置,50%时位于中央
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10, //圆角
borderColor: '#fff',
borderWidth: 2
},
},
],
};

外圈+内圈的样式

官网参考:Examples - Apache ECharts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ECharts 配置项
const options = {
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'], // 内圈以及外圈的大小
center: ['30%', '50%'], // 图的位置,距离左跟上的位置
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 30, // 内圈在选中是的字体大小
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
},
],
};

不使用flex的情况下位于一行中(⚠️)

使用inline-block布局

1
2
3
4
5
6
7
8
9
<div class="">
<p class="text-2xl inline-block">76.345</p>
<div class="inline-block bg-[#5DB1FF] rounded-3xl p-2">
<div class="flex justify-center items-center gap-2">
<img class="size-4" src="../assets/images/trending-up-white@2x.png">
<p class="text-xs text-white">23.5% (+10)</p>
</div>
</div>
</div>

点击菜单(‼️⚠️)

重点记忆下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
<div class="StatusSelection">
<div v-for="(item, index) in items" :key="index" class="item" @click="handleItemClick(index)" :class="{ active: selectedIndex === index }">
<p>{{ item }}</p>
</div>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const items = ref(['全部订单', '待付款', '代发货', '已发货', '退款中', '已完成']);
const selectedIndex = ref(-1);

const handleItemClick = (index: number) => {
selectedIndex.value = index;
};
</script>

<style scoped>
.StatusSelection {
display: flex;
justify-content: flex-start;
align-content: center;
gap: 20px;
min-height: 80px; /* 为item多留出boder的距离 */
padding: 16px;

.item {
cursor: pointer;
transition: all 0.1s ease-out;
padding: 8px 16px;

&:hover {
color: var(--accent-100);
border-bottom: 2px solid var(--accent-100);
}

&.active {
color: var(--accent-100);
border-bottom: 2px solid var(--accent-100);
}
}
}
</style>

el-tag使用(⚠️)

1
<el-tag :type="row.orderType">{{ row.orderStatus }}</el-tag>

圆角改变

父元素的padding挡住子元素,导致子元素无法click的BUG(⚠️)

原文参考:CSS - 元素遮挡(层级/定位等等)导致无法点击下层元素解决方案_css元素被遮挡-CSDN博客

为父元素加上使用 鼠标穿透属性 pointer-events就行

有bug,这样调整完后,父元素本来可以点击的内容又不能点击了

最后发现改为margin就行

1
2
3
4
/* 给父元素(遮挡的元素) */
.div{
pointer-events: none;
}

历史导航栏(⚠️)

其他

Git提交信息规范

  1. 提交信息结构:

    • 提交信息分为类型、可选的范围和描述三个部分。
    • 示例:feat(user-auth): 增加密码重置功能
  2. 类型(Type):

    • feat: 新功能
    • fix: 修复问题
    • docs: 文档变更
    • style: 代码格式、空格等非逻辑性的变更
    • refactor: 重构代码,既不是新增功能也不是修复bug
    • test: 添加或修改测试
    • chore: 构建过程或辅助工具的变更
  3. 可选的范围(Scope):

    • 描述变更影响的范围,可以是模块、组件、功能等。例如:(user-auth)
  4. 描述(Description):

    • 提供详细的变更描述,解释为什么做出这个变更以及变更的具体内容。
  5. 提交信息长度:

    • 保持简短,最好不超过50个字符。如果需要更详细的描述,可以使用空行后添加更多信息。
  6. 动词使用:

    • 使用动词的现在时来描述变更,例如:增加修复更新等。
  7. 参考任务或问题编号:

    • 如果有相关的任务、问题或需求编号,可以在提交信息中引用,以便于跟踪。例如:修复 #123

示例提交信息:

1
2
3
4
5
6
feat(user-auth): 增加密码重置功能

- 增加密码重置的新接口
- 实现了密码重置的邮件通知功能
- 更新用户认证服务以处理密码重置请求
chore(init): 初始化项目结构

这样的规范有助于更清晰地表达每次提交的目的和内容,有助于团队协作和代码维护。

Copilot使用

![截屏2024-01-24 10.35.54](/Users/tec/Library/Application Support/typora-user-images/截屏2024-01-24 10.35.54.png)

Git怎么修改提交的注释信息

原文链接:[Git使用小技巧【修改commit注释, 超详细】_git更改commit描述-CSDN博客](https://blog.csdn.net/xiaoyulike/article/details/119176756#:~:text=二、修改以前提交的注释 1 (1)git rebase -i HEAD~2 【 2,(4)%3Awq 【保存退出】 5 (5)git commit –amend 【同上有提示,第一行进行你真正需要的修改%2C 修改完后,保存退出】)

如果仅仅是想修改最后一次注释

(1)git commit –amend 【第一行出现注释界面】

(2)i 【进入修改模式, 修改完成】

(3)Esc 【退出编辑模式】

(4):wq 【保存并退出即可】

(5)git log 【 查看提交记录】

注意按住q可以退出,不要使用关掉终端的方式,否则会报以下的错误,主要是因为上个打开的文件没关,形成了一个临时文件

参考链接:成功解决:使用vim修改文件时报错Another program may be editing the same file. If this is the case的问题_another program may be editing the same file. if t-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E325: ATTENTION
Found a swap file by the name "~/NFT-Platform/.git/.COMMIT_EDITMSG.swp"
owned by: tec dated: 一 1 29 22:25:26 2024
file name: ~tec/NFT-Platform/.git/COMMIT_EDITMSG
modified: YES
user name: tec host name: TECdeMacBook-Pro.local
process ID: 15259
While opening file "/Users/tec/NFT-Platform/.git/COMMIT_EDITMSG"
dated: 一 1 29 22:27:40 2024
NEWER than swap file!

(1) Another program may be editing the same file. If this is the case,
be careful not to end up with two different instances of the same
file when making changes. Quit, or continue with caution.
(2) An edit session for this file crashed.
If this is the case, use ":recover" or "vim -r /Users/tec/NFT-Platform/.git/COMMIT_EDITMSG"

解决方法为删除该临时文件就行

1
sudo rm -rf /Users/tec/NFT-Platform/.git/.COMMIT_EDITMSG.swp

然后提交到远程仓库就行

1
git push --force origin master