Nginx基础概念和常用操作

Nginx基础概念和常用操作

1. 安装、启动、连接

直接拿docker安装,拉取镜像,运行,开放端口。

1
docker pull nginx:stable-perl

虽然在容器中性能有所损失,但是方便,加快学习进度。

启动后浏览器访问localhost,写着Welcome等欢迎语,就是启动成功了。

在VSCode下载一些Docker常用插件,然后就可以拿VSCode直接连上’正在运行的容器’,和连接Linux服务器一样。

输入nginx -V,查看nginx信息,包括安装目录、编译参数、配置文件位置、日志文件位置等信息。可以看到配置文件位于--conf-path=/etc/nginx/nginx.conf

1
2
3
4
5
6
root@f4f6c922d837:/# nginx -V
nginx version: nginx/1.26.1
built by gcc 12.2.0 (Debian 12.2.0-14)
built with OpenSSL 3.0.11 19 Sep 2023
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_v3_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -ffile-prefix-map=/data/builder/debuild/nginx-1.26.1/debian/debuild-base/nginx-1.26.1=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'

2. 快速尝试部署网站

查看nginx配置文件,可以找到如下部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80;
listen [::]:80;
server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

这意味着,nginx 把所有对服务器根路径的请求,代理到 /usr/share/nginx/html 目录下,并且把 index.html 或 index.htm 作为默认页面。

如果你使用Vue或者Hex开发网站,它们的打包输出目录,大致结构分别如下

1
2
3
4
5
6
7
dist/
├── css/
│ └── app.12345.css
├── js/
│ └── app.12345.js
├── index.html
└── favicon.ico
1
2
3
4
5
6
7
8
9
10
public/
├── css/
│ └── style.css
├── js/
│ └── script.js
├── index.html
├── about/
│ └── index.html
└── archives/
└── index.html

你只需要把dist或者public下的所有文件,直接复制到/usr/share/nginx/html目录下,重启nginx即可。

3. 配置文件

这个版本的nginx自带2个配置文件,首先是nginx.conf。

如果你修改了配置文件,可以用nginx -t来校验配置是否合法。然后使用nginx -s reload来重新加载配置文件。

1. nginx.conf

全局配置

1
2
3
4
5
user  nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
  1. user 指令:

    • 用法:user <username>;
    • 描述:指定 Nginx 运行时使用的系统用户。
    • 示例:user nginx; 表示使用 nginx 用户。
  2. worker_processes 指令:

    • 用法:worker_processes <number|auto>;
    • 描述:设置工作进程数,auto 会自动根据 CPU 核心数设置。
    • 示例:worker_processes 4; 表示使用 4 个工作进程。
  3. error_log 指令:

    • 用法:error_log <file> <level>;
    • 描述:设置错误日志的路径和日志级别。
    • 日志级别选项:
      • debug:调试信息
      • info:一般信息
      • notice:通知信息(默认)
      • warn:警告信息
      • error:错误信息
      • crit:严重错误信息
      • alert:需要立即处理的问题
      • emerg:紧急情况
    • 示例:error_log /var/log/nginx/error.log notice; 表示记录通知级别及以上的日志。
  4. pid 指令:

    • 用法:pid <file>;
    • 描述:指定存储 Nginx 进程 ID 的文件路径。
    • 示例:pid /var/run/nginx.pid; 表示将 PID 存储在 /var/run/nginx.pid 文件中。

可能改动的部分:

  1. worker_processes auto;

    • 自动设置为 CPU 核心数,适用于大多数情况。

    • 可以手动设置为特定的进程数以微调性能,但一般不需要。

事件模块

1
2
3
events {
worker_connections 1024;
}
  1. worker_connections 指令:
    • 用法:worker_connections <number>;
    • 描述:设置每个工作进程允许的最大连接数。
    • 示例:worker_connections 1024; 表示每个工作进程允许最多 1024 个连接。

可能改动的部分:

  1. worker_connections 1024;

    • 默认设置为 1024,适用于大多数小到中型网站。

    • 对于高流量网站,可以增加连接数,以提高并发处理能力。

    • 需要根据系统的 ulimit 设置进行调整,确保系统支持更多的连接。

HTTP 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;

include /etc/nginx/conf.d/*.conf;
}
  1. include 指令:

    • 用法:include <file|directory>;
    • 描述:包含指定的文件或目录下的所有配置文件。
    • 示例:include /etc/nginx/mime.types; 包含 MIME 类型配置文件。
  2. default_type 指令:

    • 用法:default_type <MIME-type>;
    • 描述:设置默认的 MIME 类型。
    • 示例:default_type application/octet-stream; 表示未能确定文件类型时使用 application/octet-stream
  3. log_format 指令:

    • 用法:log_format <name> <format>;

    • 描述:定义日志格式。

    • 示例:

      1
      2
      3
      log_format main '$remote_addr - $remote_user [$time_local] "$request" '
      '$status $body_bytes_sent "$http_referer" '
      '"$http_user_agent" "$http_x_forwarded_for"';

      定义名为 main 的日志格式。

  4. access_log 指令:

    • 用法:access_log <file> <format>;
    • 描述:设置访问日志的路径和日志格式。
    • 示例:access_log /var/log/nginx/access.log main; 使用 main 格式记录访问日志。
  5. sendfile 指令:

    • 用法:sendfile <on|off>;
    • 描述:启用或禁用 sendfile 选项,用于提高文件传输效率。
    • 示例:sendfile on; 启用 sendfile
  6. tcp_nopush 指令:

    • 用法:tcp_nopush <on|off>;
    • 描述:用于优化传输数据时的 TCP 性能,通常与 sendfile 一起使用。
    • 示例:#tcp_nopush on; 默认注释掉。
  7. keepalive_timeout 指令:

    • 用法:keepalive_timeout <timeout>;
    • 描述:设置保持客户端连接的超时时间,单位是秒。
    • 示例:keepalive_timeout 65; 设置超时时间为 65 秒。
  8. gzip 指令:

    • 用法:gzip <on|off>;
    • 描述:启用或禁用 gzip 压缩。
    • 示例:#gzip on; 默认注释掉。
  9. include指令

    • 用法:include <file|directory>;
    • 描述:include 指令用于包含其他配置文件或目录中的所有配置文件。这可以帮助将配置文件分割成更小的部分,以便于管理和维护。
    • 示例:
      include /etc/nginx/mime.types;:包含 MIME 类型配置文件。
      include /etc/nginx/conf.d/*.conf;:包含 /etc/nginx/conf.d/ 目录下的所有以 .conf 结尾的配置文件。

可能改动的部分:

  1. keepalive_timeout 65;

    • 默认设置为 65 秒,适用于大多数情况。

    • 对于高并发的场景,可以适当调低以减少资源占用。

  2. tcp_nopush on;

    • 通常注释掉,如果启用,可以优化数据传输,尤其是在发送大文件时。

    • 启用 tcp_nopush 后,Nginx 会尝试将数据块一次性发送到网络,而不是逐个包地发送,从而减少包的数量,提高传输效率。

    • 通常与sendfile on;一起使用。启用 sendfile 后,数据在内核空间中直接从一个文件描述符传输到另一个文件描述符,避免了在用户空间和内核空间之间的多次拷贝,从而提高了传输效率。

  3. gzip on;

    • 通常开启后,可以启用 gzip 压缩以减少传输数据量,提高传输速度。

    • 启用后还需配置 gzip_types 和其他参数,以确保正确压缩所需的文件类型。如

      1
      2
      3
      4
      http {
      gzip on;
      gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
      }
    • 其他 gzip 配置选项

      • gzip_min_length:设置压缩的最小文件大小。
      • gzip_comp_level:设置压缩级别(1-9),级别越高压缩率越大,但消耗的 CPU 资源更多。
      • gzip_buffers:设置用于存储压缩结果的缓冲区大小。
      • gzip_vary:设置 Vary: Accept-Encoding 响应头,指示代理服务器和浏览器缓存可以根据请求的 Accept-Encoding 头进行不同的缓存。
    • Gzip 压缩是通过压缩算法(如 DEFLATE)将 HTTP 响应内容压缩成更小的体积,然后再发送给客户端。解压缩的过程是nginx和浏览器自动完成的。

性能优化建议

  • **增加 worker_connections**:

    • 提高每个工作进程的最大连接数可以显著提升 Nginx 的并发处理能力。
    • 需要确保系统的文件描述符限制足够高。
  • 启用 gzip 压缩

    • 减少传输数据量,尤其适用于文本类型的资源(如 HTML、CSS、JavaScript)。

    • 配置示例:
      nginx
      复制代码
      gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  • **优化 keepalive_timeout**:

    • 在高并发情况下,适当调低 keepalive 超时时间以减少长时间占用连接资源。
  • **使用 tcp_nopushtcp_nodelay**:

    • 对于需要优化数据传输性能的场景,可以启用 tcp_nopushtcp_nodelay

2. default.conf

好的,让我们对 default.conf 文件的每一行进行解析:

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
listen 80;
listen [::]:80;
server_name localhost;

#access_log /var/log/nginx/host.access.log main;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

基本设置

  1. **server { ... }**:

    • 定义一个虚拟服务器块,包含服务器的配置。
  2. **listen 80;**:

    • 监听 IPv4 的 80 端口,用于处理 HTTP 请求。
  3. **listen [::]:80;**:

    • 监听 IPv6 的 80 端口,用于处理 HTTP 请求。
  4. **server_name localhost;**:

    • 定义服务器的名称为 localhost,用于匹配请求的 Host 头。

日志设置

  1. **#access_log /var/log/nginx/host.access.log main;**:
    • 配置访问日志的路径和格式,此处被注释掉。如果启用,将使用 main 日志格式记录访问日志。

根路径设置

  1. **location / { ... }**:

    • 配置根路径(即所有请求)的处理方式。
  2. **root /usr/share/nginx/html;**:

    • 指定请求的根目录为 /usr/share/nginx/html
  3. **index index.html index.htm;**:

    • 指定默认的索引文件为 index.htmlindex.htm

4. 反向代理

正向代理是代理客户端,客户端对于服务端来说是不可见的。

反向代理是代理服务端,服务端对于客户端来说是不可见的。

1. 模拟3个Web

1
docker pull python:3.9-slim

在桌面或任意地方新建app.py,内容为

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask
import os

app = Flask(__name__)

@app.route('/')
def hello():
return f"Hello from the backend server running on port {os.environ.get('PORT')}!"

if __name__ == "__main__":
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))

启动3个python容器,模拟3个web服务。

1
2
3
4
5
6
7
8
C:\Users\mumu\Desktop>docker run --name web1 -d -e PORT=8081 -p 8081:8081 -v /c/Users/mumu/Desktop/app.py:/app/app.py python:3.9-slim sh -c "pip install flask && python /app/app.py"
ab2ded77eafa82fbf4027f5714e5cb71bcb9775696f0e8c5423a70a7916a80ac

C:\Users\mumu\Desktop>docker run --name web2 -d -e PORT=8082 -p 8082:8082 -v /c/Users/mumu/Desktop/app.py:/app/app.py python:3.9-slim sh -c "pip install flask && python /app/app.py"
e6c5321ea3926157dc2f2d9ed38df53c715da51c496c5ff0e7ff0c4e68c85696

C:\Users\mumu\Desktop>docker run --name web3 -d -e PORT=8083 -p 8083:8083 -v /c/Users/mumu/Desktop/app.py:/app/app.py python:3.9-slim sh -c "pip install flask && python /app/app.py"
f0bea8ac956af515fe35159fe94c86e919dfd566b782d06641472621a6299e26

需要保证nginx和3个python在同一个docker网络中,默认应该都是bridge。

2. 链接

使用docker inspect bridge查看刚才3个容器的ip。然后修改nginx的default.conf文件。

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
upstream backend {
server 172.17.0.3:8081;
server 172.17.0.4:8082;
server 172.17.0.5:8083;
}

server {
listen 80;
listen [::]:80;
server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

location /app {
proxy_pass http://backend/;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

  • **upstream backend**:定义了一个名为 backend 的上游服务器组。

  • **location /app { ... }**:配置如何处理以 /app 开头的所有请求。

  • **proxy_pass http://backend/;**:将这些请求代理到上游服务器组 backend。请求的路径 /app 的前缀将被去除,然后传递给上游服务器。例如,/app/foo 将被代理为 /foo

重启服务。

访问localhost/app并多次刷新试试。

5. 负载均衡

在不断刷新页面会发现,反向代理是以“轮询”的方式,将请求分发到3个web服务之一的。

除了轮询之外,还有以下方式。

1. 加权轮询,Weighted Round Robin

可以为不同的服务器设置权重,权重越高,服务器被请求的概率越大。

1
2
3
4
5
upstream backend {
server 172.17.0.3:8081 weight=3;
server 172.17.0.4:8082 weight=2;
server 172.17.0.5:8083 weight=1;
}

weight默认为1 。

2. 最少连接,Least Connections

将请求分发给当前连接数最少的服务器,适用于处理时间长、连接占用多的请求。

1
2
3
4
5
6
upstream backend {
least_conn;
server 172.17.0.3:8081;
server 172.17.0.4:8082;
server 172.17.0.5:8083;
}

3. IP哈希,IP Hash

根据客户端 IP 地址进行哈希计算,保证同一客户端的请求总是转发到同一台服务器,适用于会话保持。

1
2
3
4
5
6
upstream backend {
ip_hash;
server 172.17.0.3:8081;
server 172.17.0.4:8082;
server 172.17.0.5:8083;
}

4. 哈希,Hash

根据自定义的键值进行哈希计算来分发请求。

1
2
3
4
5
6
upstream backend {
hash $request_uri;
server 172.17.0.3:8081;
server 172.17.0.4:8082;
server 172.17.0.5:8083;
}

可以使用以下变量作为键值:

  • $remote_addr:客户端 IP 地址
  • $request_uri:请求的 URI
  • $http_cookie:请求的 Cookie

ip_hashhash $remote_addr一样都可以实现同一个客户端的请求分配到同一个后端服务器。但是一般这种情况使用前者更多。

6. HTTPS

首先要安装有openssl,然后创建一个存放证书和私钥的文件夹,比如mkdir C:\nginx\ssl

执行

1
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout C:\nginx\ssl\nginx-selfsigned.key -out C:\nginx\ssl\nginx-selfsigned.crt

按问题填写信息,就可以。

得到2个文件后,复制到容器中,比如/etc/nginx/ssl目录下,然后再default.conf中添加一个server块,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name localhost;

ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

location /app {
proxy_pass http://backend/;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

打开浏览器访问https://localhost

7. 虚拟主机

一个server块就是一个虚拟主机。

新建一个conf文件和default.conf同一目录。

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
upstream backend2 {
server 172.17.0.3:8081 weight=2;
server 172.17.0.4:8082 weight=2;
server 172.17.0.5:8083 weight=6;
}

server {
listen 81;
listen [::]:81;
server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

location /app {
proxy_pass http://backend2/;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

重启服务,浏览器访问http://localhost:81/app

利用这种方式,我们可以在同一台机器上部署多个网站。

Vue3 v3.4之前如何实现组件中多个值的双向绑定?

Vue3 v3.4之前如何实现组件中多个值的双向绑定?

官方给的例子是关于el-input的,如下。但是@input不是所有组件标签都有的属性啊,有没有一种通用的办法呢?

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
defineProps({

firstName: String,
lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>

基础代码

以一个Dialog组件为例。我们自己写一个course-buy.vue

language-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
36
37
<template>
<el-dialog
v-model="localValue.dialogVisible"
title="Warning"
width="500"
align-center
>
<span>Open the dialog from the center from the screen</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="localValue.dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="localValue.dialogVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: props.dialogVisible,
courseId: props.courseId
})

</script>

外部在使用时(假设为base.vue),如下使用

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<CourseBuy
v-model:dialog-visible="orderPayParams.dialogVisible"
v-model:course-id="orderPayParams.courseId"
/>
</template>
<script setup lang="ts">
const orderPayParams = reactive({

dialogVisible: false,
courseId: 1
});
</script>

上述代码,course-buy.vue中真正使用的变量是localValue本地变量,localValue的值来自base.vue
但是上述的基础代码,dialogVisiblecourseId的值只能从base.vue流向course-buy.vue
如何实现course-buy.vue本身修改localValue的值后,修改变化同步到base.vue呢?

1. watch

如果要让dialogVisible双向绑定,可以写两个watch互相监听并更新。要实现courseId双向绑定也是同理。

language-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
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: props.dialogVisible,
courseId: props.courseId
})
//值双向绑定
watch(() => props.dialogVisible, (newValue) => {

localValue.dialogVisible = newValue;
});
watch(() => localValue.dialogVisible, (newValue) => {

emit('update:dialogVisible', newValue);
});
</script>

2. computed(推荐)

不过使用computed可以更简洁,性能也更好。

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: computed({

get: () => props.dialogVisible,
set: (value) => emit('update:dialogVisible', value)
}),
courseId: props.courseId
})
</script>
Vue3炫酷商品卡牌 组件设计

Vue3炫酷商品卡牌 组件设计

感谢来自BinaryMoon-CSS 艺术之暗系魔幻卡牌的博文。💕

演示

代码

接口类型

language-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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
export interface CourseBaseVO {

/**
* 主键
*/
id: string | number;

/**
* 机构ID
*/
companyId: string | number;

/**
* 课程名称
*/
name: string;

/**
* 大分类
*/
mt: string;

/**
* 小分类
*/
st: string;

/**
* 课程图片
*/
pic: string;

/**
* 是否收费
*/
charge: boolean;

/**
* 原价
*/
originalPrice: number;

/**
* 现价
*/
price: number;

/**
* 评分
*/
star: number;

/**
* UNPUBLISHED(1, "未发布"), UNDER_REVIEW(2, "审核中"), REVIEW_FAILED(3, "审核不通过"), REVIEW_PASSED(4, "审核通过")
*/
status: number;

/**
* 审核意见
*/
mind: string;

}
interface CourseBaseExtraHotVo extends CourseBaseVO {

isHot: boolean;
}

外部资源wave_orange.svg

language-txt
1
<svg width="100%" height="100%" id="svg" viewBox="0 0 1440 490" xmlns="http://www.w3.org/2000/svg" class="transition duration-300 ease-in-out delay-150"><defs><linearGradient id="gradient" x1="0%" y1="51%" x2="100%" y2="49%"><stop offset="5%" stop-color="#fcb900"></stop><stop offset="95%" stop-color="#ff6900"></stop></linearGradient></defs><path d="M 0,500 L 0,0 C 90.96650717703349,54.02870813397129 181.93301435406698,108.05741626794259 268,115 C 354.066985645933,121.94258373205741 435.23444976076553,81.79904306220095 535,84 C 634.7655502392345,86.20095693779905 753.129186602871,130.7464114832536 867,132 C 980.870813397129,133.2535885167464 1090.248803827751,91.2153110047847 1185,62 C 1279.751196172249,32.78468899521531 1359.8755980861245,16.392344497607656 1440,0 L 1440,500 L 0,500 Z" stroke="none" stroke-width="0" fill="url(#gradient)" fill-opacity="0.53" class="transition-all duration-300 ease-in-out delay-150 path-0"></path><defs><linearGradient id="gradient" x1="0%" y1="51%" x2="100%" y2="49%"><stop offset="5%" stop-color="#fcb900"></stop><stop offset="95%" stop-color="#ff6900"></stop></linearGradient></defs><path d="M 0,500 L 0,0 C 111.98086124401911,108.89952153110048 223.96172248803822,217.79904306220095 335,271 C 446.0382775119618,324.20095693779905 556.133971291866,321.7033492822967 626,309 C 695.866028708134,296.2966507177033 725.5023923444976,273.3875598086125 820,274 C 914.4976076555024,274.6124401913875 1073.8564593301435,298.7464114832536 1188,257 C 1302.1435406698565,215.25358851674642 1371.0717703349283,107.62679425837321 1440,0 L 1440,500 L 0,500 Z" stroke="none" stroke-width="0" fill="url(#gradient)" fill-opacity="1" class="transition-all duration-300 ease-in-out delay-150 path-1"></path></svg>

组件源码

language-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
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<template>
<div
id="card"
style="padding: 5px;margin: 20px"
>
<el-card
shadow="hover"
style="width: 350px; border-radius: 10px"
>
<div class="wave-orange-card"></div>
<div style="display: flex;flex-direction: column;justify-content: center;align-items: center;">
<el-image
:src="fileBaseUrl+courseBase.pic"
fit="fill"
style="width: 200px"
/>
</div>
<div style="height: 30px; font-size: 20px;margin-top: 10px">
<span>{
{ courseBase.name }}</span>
</div>
<div style="height: 40px; ">
<el-rate
v-model="courseBase.star"
size="large"
show-score
text-color="#ff9900"
:score-template="courseBase.star.toString() + 'points'"
disabled
/>
</div>
<div style="height: 40px; ">
<el-tag v-if="courseBase.charge" type="warning" size="large" effect="light">
<span style="font-size: 20px;font-weight: bold">¥{
{ courseBase.price }}</span>
&nbsp;&nbsp;
<span class="slash-deleted-text" style="font-size: 14px;color: #909399">{
{ courseBase.originalPrice }}</span>
</el-tag>
<el-tag v-else type="success" size="large"><span style="font-size: 20px">免费</span></el-tag>
<span>&nbsp;&nbsp; 6w人报名</span>
</div>
</el-card>
</div>
</template>

<script lang="ts" setup>

import {
CourseBaseVO} from "@/api/course/types";
import {
PropType} from "vue";

const fileBaseUrl = import.meta.env.VITE_APP_MINIO_FILE_URL;
interface CourseBaseExtraHotVo extends CourseBaseVO {

isHot: boolean;
}
const props = defineProps({

courseBase: Object as PropType<CourseBaseExtraHotVo>,
});
const emit = defineEmits(['update:courseBase'])


</script>

<style scoped>

/* 卡片图片背景 */
:deep(.wave-orange-card){

background-image: url("src/assets/svg/wave_orange.svg");
background-repeat: no-repeat;
background-size: cover; /* 或使用 100% 100% 来确保完全覆盖 */
background-position: center; /* 根据需要调整 */
overflow: hidden; /* 避免内容溢出 */
position: absolute; /* 固定定位,不随滚动条移动 */
width: 310px; /* card的宽度为350 */
height: 200px; /*pic的大小为200*200*/
opacity: 0.6;
}

/* 删除线 */
:deep(.slash-deleted-text) {

position: relative;
overflow: hidden; /* 防止斜线溢出容器 */
}

:deep(.slash-deleted-text::after) {

content: '';
position: absolute;
left: 0;
top: 10%; /* 调整为文本高度的一半 */
width: 100%; /* 与容器同宽 */
border-bottom: 1px solid #F56C6C; /* 删除线的样式 */
transform: rotate(25deg); /* 调整角度为倾斜 */
transform-origin: left bottom;
}

/* 卡片背景 */
:deep(:root) {

--margin: 100px;
/* 上演一出黄金分割率的好戏 */
--card-width: 360px;
/* 上演一出黄金分割率的好戏 */
--card-height: calc(var(--card-height) * 1.618);
}

#card{

width: var(--card-width);
height: var(--card-height);
position: relative;
cursor: pointer;
transition: transform 0.4s ease; /* 设置放大动画的过渡效果为2秒 */
}

/* 定义自定义属性 --rotate */
@property --rotate{

/* 自定义属性的默认值 */
initial-value: 90deg;
/*
定义自定义属性允许的语法结构,
此处定义该元素仅接受角度值。
*/
syntax: '<angle>';
/* 定义该自定义属性是否允许被其他元素所继承 */
inherits: false;
}

/* 定义动画 */
@keyframes edge{

from{

--rotate: 0deg;
}
to{

--rotate: 360deg;
}
}

#card::before{

content: '';
width: 104%;
height: 102%;
background: linear-gradient(var(--rotate),
rgb(44, 251, 255), rgb(81, 154, 255), rgb(97, 57, 242));
position: absolute;
z-index: -1;
top: -1%;
left: -2%;
/* 设置边框圆角半径 */
border-radius: 0.5vw;
/*
为当前元素指定使用的动画,并将该动画的
持续时间设置为 3.5s,动画速度保持不变,
动画播放次数为无限次。
*/
animation: edge 10s linear infinite;
}

#card::after{

content: '';
width: 80%;
height: 100%;
background: linear-gradient(var(--rotate),
rgb(44, 251, 255), rgb(81, 154, 255), rgb(97, 57, 242));
position: absolute;
top: 5%;
left: 10%;
filter: blur(2vw);
z-index: -1;
/* 使用动画 */
animation: edge 3.5s linear infinite;
}

/* 卡片悬浮变化背景 */
#card:hover {

transform: scale(1.02); /* 鼠标悬浮时放大到1.1倍 */
}

#card::before, #card::after {

transition: background 1s ease; /* 将过渡应用于background,确保背景渐变的平滑变化 */
}

#card:hover::before, #card:hover::after {

background: linear-gradient(var(--rotate), #f82747, #fc5c7c, #ffc3d3); /* 渐变为淡红色 */
}

</style>

使用示例

language-html
1
2
3
4
5
6
7
8
9
<template>
<CourseCard
v-for="(course, index) in hotList"
:course-base="course"
/>
</template>
<script lang="ts" setup>
const hotList = ref<CourseBaseExtraHotVo[]>([]);
</script>
Vue3引用外部TTF字体,其实很简单!

Vue3引用外部TTF字体,其实很简单!

一、下载字体

这里推荐一个网站
字体天下

二、引入TTF字体

  1. 将已有的xx.ttf按照如下示例放入assets文件夹下
  2. 并且同级目录创建fonts.css写入以下内容
language-bash
1
2
3
4
5
6
7
@font-face {

font-family: 'my-self-font';
src: url('./XiangJiaoDaJiangJunLingGanTi-2.ttf');
font-weight: normal;
font-style: normal;
}

其中font-family: 'my-self-font';就是你自定义字体的名字

  1. main.js引入fonts.css
language-bash
1
import './assets/fonts/fonts.css'

三、使用字体

language-html
1
<span style="font-family: my-self-font">你好啊</span>
Vue3 setup路由进入以及变化问题

Vue3 setup路由进入以及变化问题

1. 起因

在Vue 3中,<script setup>语法糖提供了一种更加简洁和组合式的方式来定义组件。然而,由于<script setup>的特性,它不能直接使用beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave这些导航守卫。

但是vue-router中有两个的类似函数可以触发路由离开和变化,只需要import一下就可以。

language-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import {
onBeforeRouteLeave, onBeforeRouteUpdate } from "vue-router";

onBeforeRouteUpdate((to, from, next) => {

console.log("onBeforeRouteUpdate",to,from, typeof next)
next();
})
onBeforeRouteLeave((to, from, next)=>{

console.log("beforeRouteLeave",to,from, typeof next)
next()
})
</script>

但是却没有beforeRouteEnter的替代品。

2. 浏览过的代替方法

https://github.com/vuejs/rfcs/discussions/302#discussioncomment-2794537
https://blog.richex.cn/vue3-how-to-use-beforerouteenter-in-script-setup-syntactic-sugar.html
https://blog.csdn.net/oafzzl/article/details/125045087

但是都是在<script setup>之上新加了第二个<script>,用第二个<script>来使用无setupbeforeRouteEnter。限制很多,尤其是两个script之间互动很麻烦,甚至无法实现。

3. 还是Watch

https://juejin.cn/post/7171489778230100004
https://blog.csdn.net/m0_55986526/article/details/122827829

下面是一个当路由从"/stock/move/moveDetail""/stock/move/moveSearch"触发函数的例子。同时第一次进入时也会触发watch,就相当于beforeRouteEnter了。

language-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import {
watch} from "vue";
import {
useRouter} from "vue-router";
let router = useRouter()
// 监听当前路由变化
watch(() => router.currentRoute.value,(newPath, oldPath) => {

if (newPath != null && oldPath != null && "fullPath" in newPath && "fullPath" in oldPath && newPath["fullPath"] === "/stock/move/moveSearch" && oldPath["fullPath"] === "/stock/move/moveDetail"){

onSubmitQuiet()
}
}, {
immediate: true});
</script>
ElementPlus表单验证v-for循环问题

ElementPlus表单验证v-for循环问题

提供两个样例,主要注意<el-form-item>中的prop

样例一

language-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
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<el-form
ref="dynamicValidateForm"
:model="dynamicValidateForm"
label-width="120px"
class="demo-dynamic"
>
//重点关注el-form-item标签中的prop内容
<el-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
:label="'Domain' + index"
:prop="'domains.' + index + '.value'"
:rules="{
required: true,
message: 'domain can not be null',
trigger: 'blur',
}"
>
<el-input v-model="domain.value"></el-input>
</el-form-item>
</el-form>
</template>

<script lang="ts">
export default {

data() {

return {

dynamicValidateForm: {

domains: [
{

key: 1,
value: '',
},
],
email: '',
},
}
},
}
</script>


样例二

language-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
<template>
<el-form :model="queryParams" style="width: 100%">
<el-table :data="queryParams.items">
<el-table-column label="移动数量" width="100">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-form-item style="width: 100px;padding: 0px;margin: 0px" :prop="'items.'+scope.$index+'.moveAmount'" :rules="{ required: true, message: '不能为空', trigger: 'blur',}">
<el-input-number class="number" :controls="false" v-model="scope.row.moveAmount"></el-input-number>
</el-form-item>
</div>
</template>
</el-table-column>
</el-table>
</el-form>
</template>

<script setup>
const queryParams = reactive({

factory1: undefined,
factory2: undefined,
commentText: undefined,
items:[]
})
</script>
ElementPlus隐藏Scrollbar的横向滚动条
ElementPlus-穿梭框长宽设置

ElementPlus-穿梭框长宽设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style scoped>
:deep(.el-transfer-panel) {
height: 400px; /* 穿梭框高度 */
width: 300px;
}

:deep(.el-transfer-panel__list.is-filterable) {
height: 400px; /* 穿梭框列表高度 */
}

:deep(.el-transfer-panel__body) {
height: 400px; /* 穿梭框视图层高度 */
}
:deep(.el-transfer-panel__filter) {
width: 300px; /* 修改搜索栏的宽度 */
}
</style>
Vue3 setup组合式语法优雅地使用Echarts库

Vue3 setup组合式语法优雅地使用Echarts库

1. 安装Echarts

npm或者yarn安装

npm install echarts
yarn add echarts

2.main.js全局挂载Echarts

language-javascript
1
2
3
4
5
6
7
8
9
import {
createApp } from 'vue'
import App from './App.vue'

import * as echarts from 'echarts'

const app = createApp(App)
app.config.globalProperties.$echarts = echarts // 全局挂载echarts
app.mount('#app')

3.Vue setup组合式语法使用案例

language-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
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
<template>
<!-- 通过ref获取html元素 宽高必须设置 -->
<el-row>
<el-col :offset="8" :span="8">
<div ref="info" style="width: 100%; height: 600px"></div>
</el-col>
</el-row>
</template>
<script setup>
import {
onMounted, ref, inject } from "vue";
const {
proxy } = getCurrentInstance()//获取Vue3全局配置
const info = ref();//用来获取对应标签组件

onMounted(() => {

var infoEl = info.value;//获取ref="info"的标签组件
var userEc = proxy.$echarts.init(infoEl, "light");//proxy.$echarts是在获取全局配置中的echarts,这样就不需要在每个vue中import echarts了

//此处为图表配置区,可参考Echarts官网替换各种图表
var option = {

tooltip: {

trigger: 'item'
},
legend: {

top: '5%',
left: 'center'
},
series: [
{

name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {

borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {

show: false,
position: 'center'
},
emphasis: {

label: {

show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {

show: false
},
data: [
{
value: 1048, name: 'Search Engine' },
{
value: 735, name: 'Direct' },
{
value: 580, name: 'Email' },
{
value: 484, name: 'Union Ads' },
{
value: 300, name: 'Video Ads' }
]
}
]
};
userEc.setOption(option);//将图标挂载到标签组件
});
</script>

(完)